# Copyright The IETF Trust 2009-2020, All Rights Reserved # -*- coding: utf-8 -*- import datetime import io import json import os import random import re import shutil from unittest import skipIf from mock import patch from pyquery import PyQuery from io import StringIO, BytesIO from bs4 import BeautifulSoup from urllib.parse import urlparse, urlsplit from django.urls import reverse as urlreverse from django.conf import settings from django.contrib.auth.models import User from django.test import Client, override_settings from django.db.models import F from django.http import QueryDict from django.template import Context, Template import debug # pyflakes:ignore from ietf.doc.models import Document from ietf.group.models import Group, Role, GroupFeatures from ietf.group.utils import can_manage_group from ietf.person.models import Person from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request from ietf.meeting.helpers import send_interim_approval_request from ietf.meeting.helpers import send_interim_meeting_cancellation_notice, send_interim_session_cancellation_notice from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates from ietf.meeting.models import Session, TimeSlot, Meeting, SchedTimeSessAssignment, Schedule, SessionPresentation, SlideSubmission, SchedulingEvent, Room, Constraint, ConstraintName from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting, make_interim_test_data from ietf.meeting.utils import finalize, condition_slide_order from ietf.meeting.utils import add_event_info_to_session_qs from ietf.meeting.views import session_draft_list, parse_agenda_filter_params from ietf.name.models import SessionStatusName, ImportantDateName, RoleName from ietf.utils.decorators import skip_coverage from ietf.utils.mail import outbox, empty_outbox, get_payload_text from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent from ietf.utils.text import xslugify from ietf.person.factories import PersonFactory from ietf.group.factories import GroupFactory, GroupEventFactory, RoleFactory from ietf.meeting.factories import ( SessionFactory, SessionPresentationFactory, ScheduleFactory, MeetingFactory, FloorPlanFactory, TimeSlotFactory, SlideSubmissionFactory ) from ietf.doc.factories import DocumentFactory, WgDraftFactory from ietf.submit.tests import submission_file from ietf.utils.test_utils import assert_ical_response_is_valid if os.path.exists(settings.GHOSTSCRIPT_COMMAND): skip_pdf_tests = False skip_message = "" else: skip_pdf_tests = True skip_message = ("Skipping pdf test: The binary for ghostscript wasn't found in the\n " "location indicated in settings.py.") print(" "+skip_message) class MeetingTests(TestCase): def setUp(self): self.materials_dir = self.tempdir('materials') self.id_dir = self.tempdir('id') self.archive_dir = self.tempdir('id-archive') # os.mkdir(os.path.join(self.archive_dir, "unknown_ids")) os.mkdir(os.path.join(self.archive_dir, "deleted_tombstones")) os.mkdir(os.path.join(self.archive_dir, "expired_without_tombstone")) # self.saved_agenda_path = settings.AGENDA_PATH self.saved_id_dir = settings.INTERNET_DRAFT_PATH self.saved_archive_dir = settings.INTERNET_DRAFT_ARCHIVE_DIR # settings.AGENDA_PATH = self.materials_dir settings.INTERNET_DRAFT_PATH = self.id_dir settings.INTERNET_DRAFT_ARCHIVE_DIR = self.archive_dir def tearDown(self): shutil.rmtree(self.id_dir) shutil.rmtree(self.archive_dir) shutil.rmtree(self.materials_dir) # settings.AGENDA_PATH = self.saved_agenda_path settings.INTERNET_DRAFT_PATH = self.saved_id_dir settings.INTERNET_DRAFT_ARCHIVE_DIR = self.saved_archive_dir def write_materials_file(self, meeting, doc, content): path = os.path.join(self.materials_dir, "%s/%s/%s" % (meeting.number, doc.type_id, doc.uploaded_filename)) dirname = os.path.dirname(path) if not os.path.exists(dirname): os.makedirs(dirname) with io.open(path, "w") as f: f.write(content) def write_materials_files(self, meeting, session): draft = Document.objects.filter(type="draft", group=session.group).first() self.write_materials_file(meeting, session.materials.get(type="agenda"), "1. WG status (15 minutes)\n\n2. Status of %s\n\n" % draft.name) self.write_materials_file(meeting, session.materials.get(type="minutes"), "1. More work items underway\n\n2. The draft will be finished before next meeting\n\n") self.write_materials_file(meeting, session.materials.filter(type="slides").exclude(states__type__slug='slides',states__slug='deleted').first(), "This is a slideshow") def test_meeting_agenda(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule=meeting.schedule) # self.write_materials_files(meeting, session) # future_year = datetime.date.today().year+1 future_num = (future_year-1984)*3 # valid for the mid-year meeting future_meeting = Meeting.objects.create(date=datetime.date(future_year, 7, 22), number=future_num, type_id='ietf', city="Panama City", country="PA", time_zone='America/Panama') registration_text = "Registration" # utc time_interval = "%s-%s" % (slot.utc_start_time().strftime("%H:%M").lstrip("0"), (slot.utc_start_time() + slot.duration).strftime("%H:%M").lstrip("0")) r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number,utc='-utc'))) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) agenda_content = q("#content").html() self.assertIn(session.group.acronym, agenda_content) self.assertIn(session.group.name, agenda_content) self.assertIn(session.group.parent.acronym.upper(), agenda_content) self.assertIn(slot.location.name, agenda_content) self.assertIn(time_interval, agenda_content) self.assertIsNotNone(q(':input[value="%s"]' % meeting.time_zone), 'Time zone selector should show meeting timezone') self.assertIsNotNone(q('.nav *:contains("%s")' % meeting.time_zone), 'Time zone indicator should be in nav sidebar') # plain time_interval = "%s-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0")) r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number))) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) agenda_content = q("#content").html() self.assertIn(session.group.acronym, agenda_content) self.assertIn(session.group.name, agenda_content) self.assertIn(session.group.parent.acronym.upper(), agenda_content) self.assertIn(slot.location.name, agenda_content) self.assertIn(time_interval, agenda_content) self.assertIn(registration_text, agenda_content) # Make sure there's a frame for the session agenda and it points to the right place assignment_url = urlreverse('ietf.meeting.views.session_materials', kwargs=dict(session_id=session.pk)) self.assertTrue( any( [assignment_url in x.attrib["data-src"] for x in q('tr div.modal-body div.session-materials')] ) ) # future meeting, no agenda r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=future_meeting.number))) self.assertContains(r, "There is no agenda available yet.") self.assertTemplateUsed(r, 'meeting/no-agenda.html') # text # the rest of the results don't have as nicely formatted times time_interval = time_interval.replace(":", "") r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number, ext=".txt"))) self.assertContains(r, session.group.acronym) self.assertContains(r, session.group.name) self.assertContains(r, session.group.parent.acronym.upper()) self.assertContains(r, slot.location.name) self.assertContains(r, time_interval) r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email()))) self.assertContains(r, 'not the official schedule') # future meeting, no agenda r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=future_meeting.number, ext=".txt"))) self.assertContains(r, "There is no agenda available yet.") self.assertTemplateUsed(r, 'meeting/no-agenda.txt') # CSV r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number, ext=".csv"))) self.assertContains(r, session.group.acronym) self.assertContains(r, session.group.name) self.assertContains(r, session.group.parent.acronym.upper()) self.assertContains(r, slot.location.name) self.assertContains(r, registration_text) self.assertContains(r, session.materials.get(type='agenda').uploaded_filename) self.assertContains(r, session.materials.filter(type='slides').exclude(states__type__slug='slides',states__slug='deleted').first().uploaded_filename) self.assertNotContains(r, session.materials.filter(type='slides',states__type__slug='slides',states__slug='deleted').first().uploaded_filename) # iCal r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs=dict(num=meeting.number)) + "?show=" + session.group.parent.acronym.upper()) self.assertContains(r, session.group.acronym) self.assertContains(r, session.group.name) self.assertContains(r, slot.location.name) self.assertContains(r, "BEGIN:VTIMEZONE") self.assertContains(r, "END:VTIMEZONE") self.assertContains(r, session.agenda().get_href()) self.assertContains(r, session.materials.filter(type='slides').exclude(states__type__slug='slides',states__slug='deleted').first().get_href()) # TODO - the ics view uses .all on a queryset in a view so it's showing the deleted slides. #self.assertNotContains(r, session.materials.filter(type='slides',states__type__slug='slides',states__slug='deleted').first().get_absolute_url()) # week view r = self.client.get(urlreverse("ietf.meeting.views.week_view", kwargs=dict(num=meeting.number))) self.assertNotContains(r, 'CANCELLED') self.assertContains(r, session.group.acronym) self.assertContains(r, slot.location.name) self.assertContains(r, registration_text) # week view with a cancelled session SchedulingEvent.objects.create( session=session, status=SessionStatusName.objects.get(slug='canceled'), by=Person.objects.get(name='(System)') ) r = self.client.get(urlreverse("ietf.meeting.views.week_view", kwargs=dict(num=meeting.number))) self.assertContains(r, 'CANCELLED') self.assertContains(r, session.group.acronym) self.assertContains(r, slot.location.name) def test_meeting_agenda_filters_ignored(self): """The agenda view should ignore filter querystrings (They are handled by javascript on the front end) """ meeting = make_meeting_test_data() expected_items = meeting.schedule.assignments.exclude(timeslot__type__in=['lead','offagenda']) expected_rows = ['row-%s' % item.slug() for item in expected_items] r = self.client.get(urlreverse('ietf.meeting.views.agenda')) for row_id in expected_rows: self.assertContains(r, row_id) r = self.client.get(urlreverse('ietf.meeting.views.agenda') + '?show=mars') for row_id in expected_rows: self.assertContains(r, row_id) r = self.client.get(urlreverse('ietf.meeting.views.agenda') + '?show=mars&hide=ames,mars,plenary,ietf,bof') for row_id in expected_rows: self.assertContains(r, row_id) def test_agenda_iab_session(self): date = datetime.date.today() meeting = MeetingFactory(type_id='ietf', date=date ) make_meeting_test_data(meeting=meeting) iab = Group.objects.get(acronym='iab') venus = Group.objects.create( name="Three letter acronym", acronym="venus", description="This group discusses exploration of Venus", state_id="active", type_id="program", parent=iab, list_email="venus@ietf.org", ) venus_session = Session.objects.create( meeting=meeting, group=venus, attendees=10, requested_duration=datetime.timedelta(minutes=60), type_id='regular', ) system_person = Person.objects.get(name="(System)") SchedulingEvent.objects.create(session=venus_session, status_id='schedw', by=system_person) room = Room.objects.create(meeting=meeting, name="Aphrodite", capacity=100, functional_name="Aphrodite Room") room.session_types.add('regular') session_date = meeting.date + datetime.timedelta(days=1) slot3 = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room, duration=datetime.timedelta(minutes=60), time=datetime.datetime.combine(session_date, datetime.time(13, 30))) SchedTimeSessAssignment.objects.create(timeslot=slot3, session=venus_session, schedule=meeting.schedule) url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertContains(r, 'venus') q = PyQuery(r.content) venus_row = q('[id*="-iab-"]').html() self.assertIn('venus', venus_row) def test_agenda_current_audio(self): date = datetime.date.today() meeting = MeetingFactory(type_id='ietf', date=date ) make_meeting_test_data(meeting=meeting) url = urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertContains(r, "Audio stream") def test_agenda_by_room(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.views.agenda_by_room",kwargs=dict(num=meeting.number)) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']])) url = urlreverse("ietf.meeting.views.agenda_by_room",kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email())) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room',]])) self.assertNotContains(r, 'IESG Breakfast') def test_agenda_by_type(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number)) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']])) url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email())) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room',]])) self.assertNotContains(r, 'IESG Breakfast') url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='regular')) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room']])) self.assertFalse(any([x in unicontent(r) for x in ['IESG Breakfast','Breakfast Room']])) url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='lead')) r = self.client.get(url) self.assertFalse(any([x in unicontent(r) for x in ['mars','Test Room']])) self.assertTrue(all([x in unicontent(r) for x in ['IESG Breakfast','Breakfast Room']])) url = urlreverse("ietf.meeting.views.agenda_by_type",kwargs=dict(num=meeting.number,type='lead',name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email())) r = self.client.get(url) self.assertFalse(any([x in unicontent(r) for x in ['IESG Breakfast','Breakfast Room']])) def test_agenda_room_view(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.views.room_view",kwargs=dict(num=meeting.number)) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code,200) self.assertTrue(all([x in unicontent(r) for x in ['mars','IESG Breakfast','Test Room','Breakfast Room']])) url = urlreverse("ietf.meeting.views.room_view",kwargs=dict(num=meeting.number,name=meeting.unofficial_schedule.name,owner=meeting.unofficial_schedule.owner.email())) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ['mars','Test Room','Breakfast Room']])) self.assertNotContains(r, 'IESG Breakfast') def test_agenda_week_view(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.views.week_view",kwargs=dict(num=meeting.number)) + "?show=farfut" r = self.client.get(url) self.assertEqual(r.status_code,200) self.assertTrue(all([x in unicontent(r) for x in ['var all_items', 'maximize', 'draw_calendar', ]])) # Specifying a time zone should not change the output (time zones are handled by the JS) url = urlreverse("ietf.meeting.views.week_view",kwargs=dict(num=meeting.number)) + "?show=farfut&tz=Asia/Bangkok" r_with_tz = self.client.get(url) self.assertEqual(r_with_tz.status_code,200) self.assertEqual(r.content, r_with_tz.content) @override_settings(MEETING_MATERIALS_SERVE_LOCALLY=False, MEETING_DOC_HREFS = settings.MEETING_DOC_CDN_HREFS) def test_materials_through_cdn(self): meeting = make_meeting_test_data(create_interims=True) session107 = SessionFactory(meeting__number='172',group__acronym='mars') doc = DocumentFactory.create(name='agenda-172-mars', type_id='agenda', title="Agenda", uploaded_filename="agenda-172-mars.txt", group=session107.group, rev='00', states=[('agenda','active')]) pres = SessionPresentation.objects.create(session=session107,document=doc,rev=doc.rev) session107.sessionpresentation_set.add(pres) # doc = DocumentFactory.create(name='minutes-172-mars', type_id='minutes', title="Minutes", uploaded_filename="minutes-172-mars.md", group=session107.group, rev='00', states=[('minutes','active')]) pres = SessionPresentation.objects.create(session=session107,document=doc,rev=doc.rev) session107.sessionpresentation_set.add(pres) doc = DocumentFactory.create(name='slides-172-mars-1-active', type_id='slides', title="Slideshow", uploaded_filename="slides-172-mars.txt", group=session107.group, rev='00', states=[('slides','active'), ('reuse_policy', 'single')]) pres = SessionPresentation.objects.create(session=session107,document=doc,rev=doc.rev) session107.sessionpresentation_set.add(pres) for session in ( Session.objects.filter(meeting=meeting, group__acronym="mars").first(), session107, Session.objects.filter(meeting__type_id='interim', group__acronym='mars', schedulingevent__status='sched').first(), ): self.write_materials_files(session.meeting, session) for document in (session.agenda(),session.minutes(),session.slides()[0]): url = urlreverse("ietf.meeting.views.materials_document", kwargs=dict(num=session.meeting.number, document=document)) r = self.client.get(url) if session.meeting.number.isdigit() and int(session.meeting.number)<=96: self.assertEqual(r.status_code,200) else: self.assertEqual(r.status_code,302) self.assertEqual(r['Location'],document.get_href()) self.assertNotEqual(urlsplit(r['Location'])[2],url) def test_materials(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() self.do_test_materials(meeting, session) def test_interim_materials(self): make_meeting_test_data() group = Group.objects.get(acronym='mars') date = datetime.datetime.today() - datetime.timedelta(days=10) meeting = make_interim_meeting(group=group, date=date, status='sched') session = meeting.session_set.first() self.do_test_materials(meeting, session) @override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True) def do_test_materials(self, meeting, session): self.write_materials_files(meeting, session) # session agenda document = session.agenda() url = urlreverse("ietf.meeting.views.materials_document", kwargs=dict(num=meeting.number, document=document)) r = self.client.get(url) if r.status_code != 200: q = PyQuery(r.content) debug.show('q(".alert").text()') self.assertContains(r, "1. WG status") # session minutes url = urlreverse("ietf.meeting.views.materials_document", kwargs=dict(num=meeting.number, document=session.minutes())) r = self.client.get(url) self.assertContains(r, "1. More work items underway") cont_disp = r._headers.get('content-disposition', ('Content-Disposition', ''))[1] cont_disp = re.split('; ?', cont_disp) cont_disp_settings = dict( e.split('=', 1) for e in cont_disp if '=' in e ) filename = cont_disp_settings.get('filename', '').strip('"') if filename.endswith('.md'): for accept, cont_type, content in [ ('text/html,text/plain,text/markdown', 'text/html', '
  • More work items underway

  • '), ('text/markdown,text/html,text/plain', 'text/markdown', '1. More work items underway'), ('text/plain,text/markdown, text/html', 'text/plain', '1. More work items underway'), ('text/html', 'text/html', '
  • More work items underway

  • '), ('text/markdown', 'text/markdown', '1. More work items underway'), ('text/plain', 'text/plain', '1. More work items underway'), ]: client = Client(HTTP_ACCEPT=accept) r = client.get(url) rtype = r['Content-Type'].split(';')[0] self.assertEqual(cont_type, rtype) self.assertContains(r, content) # test with explicit meeting number in url if meeting.number.isdigit(): url = urlreverse("ietf.meeting.views.materials", kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) row = q('#content #%s' % str(session.group.acronym)).closest("tr") self.assertTrue(row.find('a:contains("Agenda")')) self.assertTrue(row.find('a:contains("Minutes")')) self.assertTrue(row.find('a:contains("Slideshow")')) self.assertFalse(row.find("a:contains(\"Bad Slideshow\")")) # test with no meeting number in url url = urlreverse("ietf.meeting.views.materials", kwargs=dict()) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) row = q('#content #%s' % str(session.group.acronym)).closest("tr") self.assertTrue(row.find('a:contains("Agenda")')) self.assertTrue(row.find('a:contains("Minutes")')) self.assertTrue(row.find('a:contains("Slideshow")')) self.assertFalse(row.find("a:contains(\"Bad Slideshow\")")) # test with a loggged-in wg chair self.client.login(username="marschairman", password="marschairman+password") url = urlreverse("ietf.meeting.views.materials", kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) row = q('#content #%s' % str(session.group.acronym)).closest("tr") self.assertTrue(row.find('a:contains("Agenda")')) self.assertTrue(row.find('a:contains("Minutes")')) self.assertTrue(row.find('a:contains("Slideshow")')) self.assertFalse(row.find("a:contains(\"Bad Slideshow\")")) self.assertTrue(row.find('a:contains("Edit materials")')) # FIXME: missing tests of .pdf/.tar generation (some code can # probably be lifted from similar tests in iesg/tests.py) # document-specific urls for doc in session.materials.exclude(states__slug='deleted'): url = urlreverse('ietf.meeting.views.materials_document', kwargs=dict(num=meeting.number, document=doc.name)) r = self.client.get(url) self.assertEqual(unicontent(r), doc.text()) def test_materials_editable_groups(self): meeting = make_meeting_test_data() self.client.login(username="marschairman", password="marschairman+password") r = self.client.get(urlreverse("ietf.meeting.views.materials_editable_groups", kwargs={'num':meeting.number})) self.assertContains(r, meeting.number) self.assertContains(r, "mars") self.assertNotContains(r, "No session requested") self.client.login(username="ad", password="ad+password") r = self.client.get(urlreverse("ietf.meeting.views.materials_editable_groups", kwargs={'num':meeting.number})) self.assertContains(r, meeting.number) self.assertContains(r, "frfarea") self.assertContains(r, "No session requested") self.client.login(username="plain",password="plain+password") r = self.client.get(urlreverse("ietf.meeting.views.materials_editable_groups", kwargs={'num':meeting.number})) self.assertContains(r, meeting.number) self.assertContains(r, "You cannot manage the meeting materials for any groups") @override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True) def test_materials_name_endswith_hyphen_number_number(self): sp = SessionPresentationFactory(document__name='slides-junk-15',document__type_id='slides',document__states=[('reuse_policy','single')]) sp.document.uploaded_filename = '%s-%s.pdf'%(sp.document.name,sp.document.rev) sp.document.save() self.write_materials_file(sp.session.meeting, sp.document, 'Fake slide contents') url = urlreverse("ietf.meeting.views.materials_document", kwargs=dict(document=sp.document.name,num=sp.session.meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code, 200) def test_proceedings(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() GroupEventFactory(group=session.group,type='status_update') SessionPresentationFactory(document__type_id='recording',session=session) SessionPresentationFactory(document__type_id='recording',session=session,document__title="Audio recording for tests") self.write_materials_files(meeting, session) url = urlreverse("ietf.meeting.views.proceedings", kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code, 200) def test_proceedings_acknowledgements(self): make_meeting_test_data() meeting = MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="96") meeting.acknowledgements = 'test acknowledgements' meeting.save() url = urlreverse('ietf.meeting.views.proceedings_acknowledgements',kwargs={'num':meeting.number}) response = self.client.get(url) self.assertContains(response, 'test acknowledgements') @patch('ietf.meeting.utils.requests.get') def test_proceedings_attendees(self, mockobj): mockobj.return_value.text = b'[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US"}]' mockobj.return_value.json = lambda: json.loads(b'[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US"}]') make_meeting_test_data() meeting = MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="96") finalize(meeting) url = urlreverse('ietf.meeting.views.proceedings_attendees',kwargs={'num':96}) response = self.client.get(url) self.assertContains(response, 'Attendee List') q = PyQuery(response.content) self.assertEqual(1,len(q("#id_attendees tbody tr"))) @patch('urllib.request.urlopen') def test_proceedings_overview(self, mock_urlopen): '''Test proceedings IETF Overview page. Note: old meetings aren't supported so need to add a new meeting then test. ''' mock_urlopen.return_value = BytesIO(b'[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US"}]') make_meeting_test_data() meeting = MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="96") finalize(meeting) url = urlreverse('ietf.meeting.views.proceedings_overview',kwargs={'num':96}) response = self.client.get(url) self.assertContains(response, 'The Internet Engineering Task Force') def test_proceedings_progress_report(self): make_meeting_test_data() MeetingFactory(type_id='ietf', date=datetime.date(2016,4,3), number="95") MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="96") url = urlreverse('ietf.meeting.views.proceedings_progress_report',kwargs={'num':96}) response = self.client.get(url) self.assertContains(response, 'Progress Report') def test_feed(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() r = self.client.get("/feed/wg-proceedings/") self.assertContains(r, "agenda") self.assertContains(r, session.group.acronym) def test_important_dates(self): meeting=MeetingFactory(type_id='ietf') meeting.show_important_dates = True meeting.save() populate_important_dates(meeting) url = urlreverse('ietf.meeting.views.important_dates',kwargs={'num':meeting.number}) r = self.client.get(url) self.assertContains(r, str(meeting.importantdate_set.first().date)) idn = ImportantDateName.objects.filter(used=True).first() pre_date = meeting.importantdate_set.get(name=idn).date idn.default_offset_days -= 1 idn.save() update_important_dates(meeting) post_date = meeting.importantdate_set.get(name=idn).date self.assertEqual(pre_date, post_date+datetime.timedelta(days=1)) def test_important_dates_ical(self): meeting = MeetingFactory(type_id='ietf') meeting.show_important_dates = True meeting.save() populate_important_dates(meeting) url = urlreverse('ietf.meeting.views.important_dates', kwargs={'num': meeting.number, 'output_format': 'ics'}) r = self.client.get(url) for d in meeting.importantdate_set.all(): self.assertContains(r, d.date.isoformat()) def test_group_ical(self): meeting = make_meeting_test_data() s1 = Session.objects.filter(meeting=meeting, group__acronym="mars").first() a1 = s1.official_timeslotassignment() t1 = a1.timeslot # Create an extra session t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30))) s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False) SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule) # url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'acronym':s1.group.acronym, }) r = self.client.get(url) assert_ical_response_is_valid(self, r, expected_event_summaries=['mars - Martian Special Interest Group'], expected_event_count=2) self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S')) self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S')) # url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, }) r = self.client.get(url) assert_ical_response_is_valid(self, r, expected_event_summaries=['mars - Martian Special Interest Group'], expected_event_count=1) self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S')) self.assertNotContains(r, t2.time.strftime('%Y%m%dT%H%M%S')) def test_meeting_agenda_has_static_ical_links(self): """Links to the agenda_ical view must appear on the agenda page Confirms that these have the correct querystrings. Does not test the JS-based 'Customized schedule' button. """ meeting = make_meeting_test_data() # get the agenda url = urlreverse('ietf.meeting.views.agenda', kwargs=dict(num=meeting.number)) r = self.client.get(url) # Check that it has the links we expect ical_url = urlreverse('ietf.meeting.views.agenda_ical', kwargs=dict(num=meeting.number)) q = PyQuery(r.content) content = q('#content').html() assignments = meeting.schedule.assignments.exclude(timeslot__type__in=['lead', 'offagenda']) # Assume the test meeting is not using historic groups groups = [a.session.group for a in assignments if a.session is not None] for g in groups: if g.parent_id is not None: self.assertIn('%s?show=%s' % (ical_url, g.parent.acronym.lower()), content) # Should be a 'non-area events' link showing appropriate types non_area_labels = [ 'BoF', 'EDU', 'Hackathon', 'IEPG', 'IESG', 'IETF', 'Plenary', 'Secretariat', 'Tools', ] self.assertIn('%s?show=%s' % (ical_url, ','.join(non_area_labels).lower()), content) def test_parse_agenda_filter_params(self): def _r(show=(), hide=(), showtypes=(), hidetypes=()): """Helper to create expected result dict""" return dict(show=set(show), hide=set(hide), showtypes=set(showtypes), hidetypes=set(hidetypes)) self.assertIsNone(parse_agenda_filter_params(QueryDict(''))) # test valid combos (not exhaustive) for qstr, expected in ( ('show=', _r()), ('hide=', _r()), ('showtypes=', _r()), ('hidetypes=', _r()), ('show=x', _r(show=['x'])), ('hide=x', _r(hide=['x'])), ('showtypes=x', _r(showtypes=['x'])), ('hidetypes=x', _r(hidetypes=['x'])), ('show=x,y,z', _r(show=['x','y','z'])), ('hide=x,y,z', _r(hide=['x','y','z'])), ('showtypes=x,y,z', _r(showtypes=['x','y','z'])), ('hidetypes=x,y,z', _r(hidetypes=['x','y','z'])), ('show=a&hide=a', _r(show=['a'], hide=['a'])), ('show=a&hide=b', _r(show=['a'], hide=['b'])), ('show=a&hide=b&showtypes=c&hidetypes=d', _r(show=['a'], hide=['b'], showtypes=['c'], hidetypes=['d'])), ): self.assertEqual( parse_agenda_filter_params(QueryDict(qstr)), expected, 'Parsed "%s" incorrectly' % qstr, ) def do_ical_filter_test(self, meeting, querystring, expected_session_summaries): url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number}) r = self.client.get(url + querystring) self.assertEqual(r.status_code, 200) assert_ical_response_is_valid(self, r, expected_event_summaries=expected_session_summaries, expected_event_count=len(expected_session_summaries)) def test_ical_filter(self): # Just a quick check of functionality - permutations tested via tests_js.AgendaTests meeting = make_meeting_test_data() self.do_ical_filter_test( meeting, querystring='', expected_session_summaries=[ 'Morning Break', 'Registration', 'IETF Plenary', 'ames - Asteroid Mining Equipment Standardization Group', 'mars - Martian Special Interest Group', ] ) self.do_ical_filter_test( meeting, querystring='?show=plenary,secretariat,ames&hide=reg', expected_session_summaries=[ 'Morning Break', 'IETF Plenary', 'ames - Asteroid Mining Equipment Standardization Group', ] ) def build_session_setup(self): # This setup is intentionally unusual - the session has one draft attached as a session presentation, # but lists a different on in its agenda. The expectation is that the pdf and tgz views will return both. session = SessionFactory(group__type_id='wg',meeting__type_id='ietf') draft1 = WgDraftFactory(group=session.group) session.sessionpresentation_set.create(document=draft1) draft2 = WgDraftFactory(group=session.group) agenda = DocumentFactory(type_id='agenda',group=session.group, uploaded_filename='agenda-%s-%s' % (session.meeting.number,session.group.acronym), states=[('agenda','active')]) session.sessionpresentation_set.create(document=agenda) self.write_materials_file(session.meeting, session.materials.get(type="agenda"), "1. WG status (15 minutes)\n\n2. Status of %s\n\n" % draft2.name) filenames = [] for d in (draft1, draft2): file,_ = submission_file(name=d.name,format='txt',templatename='test_submission.txt',group=session.group,rev="00") filename = os.path.join(d.get_file_path(),file.name) with io.open(filename,'w') as draftbits: draftbits.write(file.getvalue()) filenames.append(filename) self.assertEqual( len(session_draft_list(session.meeting.number,session.group.acronym)), 2) return (session, filenames) def test_session_draft_tarfile(self): session, filenames = self.build_session_setup() url = urlreverse('ietf.meeting.views.session_draft_tarfile', kwargs={'num':session.meeting.number,'acronym':session.group.acronym}) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.get('Content-Type'), 'application/octet-stream') for filename in filenames: os.unlink(filename) @skipIf(skip_pdf_tests, skip_message) @skip_coverage def test_session_draft_pdf(self): session, filenames = self.build_session_setup() url = urlreverse('ietf.meeting.views.session_draft_pdf', kwargs={'num':session.meeting.number,'acronym':session.group.acronym}) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.get('Content-Type'), 'application/pdf') for filename in filenames: os.unlink(filename) def test_current_materials(self): url = urlreverse('ietf.meeting.views.current_materials') response = self.client.get(url) self.assertEqual(response.status_code, 404) MeetingFactory(type_id='ietf', date=datetime.date.today()) response = self.client.get(url) self.assertEqual(response.status_code, 302) def test_edit_schedule_properties(self): self.client.login(username='secretary',password='secretary+password') url = urlreverse('ietf.meeting.views.edit_schedule_properties',kwargs={'owner':'does@notexist.example','name':'doesnotexist','num':00}) response = self.client.get(url) self.assertEqual(response.status_code,404) self.client.logout() schedule = ScheduleFactory(meeting__type_id='ietf',visible=False,public=False) url = urlreverse('ietf.meeting.views.edit_schedule_properties',kwargs={'owner':schedule.owner.email(),'name':schedule.name,'num':schedule.meeting.number}) response = self.client.get(url) self.assertEqual(response.status_code,302) self.client.login(username='secretary',password='secretary+password') response = self.client.get(url) self.assertEqual(response.status_code,200) new_base = Schedule.objects.create(name="newbase", owner=schedule.owner, meeting=schedule.meeting) response = self.client.post(url, { 'name':schedule.name, 'visible':True, 'public':True, 'notes': "New Notes", 'base': new_base.pk, } ) self.assertNoFormPostErrors(response) schedule.refresh_from_db() self.assertTrue(schedule.visible) self.assertTrue(schedule.public) self.assertEqual(schedule.notes, "New Notes") self.assertEqual(schedule.base_id, new_base.pk) def test_agenda_by_type_ics(self): session=SessionFactory(meeting__type_id='ietf',type_id='lead') url = urlreverse('ietf.meeting.views.agenda_by_type_ics',kwargs={'num':session.meeting.number,'type':'lead'}) login_testing_unauthorized(self,"secretary",url) response = self.client.get(url) self.assertEqual(response.status_code,200) self.assertEqual(response.get('Content-Type'), 'text/calendar') def test_cancelled_ics(self): session=SessionFactory(meeting__type_id='ietf',status_id='canceled') url = urlreverse('ietf.meeting.views.agenda_ical', kwargs=dict(num=session.meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code,200) self.assertIn('STATUS:CANCELLED',unicontent(r)) self.assertNotIn('STATUS:CONFIRMED',unicontent(r)) def test_session_materials(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() url = urlreverse('ietf.meeting.views.session_materials', kwargs=dict(session_id=session.pk)) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) agenda_div = q('div.agenda-frame') self.assertIsNotNone(agenda_div) self.assertEqual(agenda_div.attr('data-src'), session.agenda().get_href()) minutes_div = q('div.minutes-frame') self.assertIsNotNone(minutes_div) self.assertEqual(minutes_div.attr('data-src'), session.minutes().get_href()) # Make sure undeleted slides are present and deleted slides are not not_deleted_slides = session.materials.filter( type='slides' ).exclude( states__type__slug='slides',states__slug='deleted' ) self.assertGreater(not_deleted_slides.count(), 0) # make sure this isn't a pointless test deleted_slides = session.materials.filter( type='slides', states__type__slug='slides', states__slug='deleted' ) self.assertGreater(deleted_slides.count(), 0) # make sure this isn't a pointless test # live slides should be found for slide in not_deleted_slides: self.assertTrue(q('ul li a:contains("%s")' % slide.title)) # deleted slides should not be found for slide in deleted_slides: self.assertFalse(q('ul li a:contains("%s")' % slide.title)) class ReorderSlidesTests(TestCase): def test_add_slides_to_session(self): for type_id in ('ietf','interim'): chair_role = RoleFactory(name_id='chair') session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90), meeting__type_id=type_id) slides = DocumentFactory(type_id='slides') url = urlreverse('ietf.meeting.views.ajax_add_slides_to_session', kwargs={'session_id':session.pk, 'num':session.meeting.number}) # Not a valid user r = self.client.post(url, {'order':1, 'name':slides.name }) self.assertEqual(r.status_code, 403) self.assertIn('have permission', unicontent(r)) self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password") # Past submission cutoff r = self.client.post(url, {'order':0, 'name':slides.name }) self.assertEqual(r.status_code, 403) self.assertIn('materials cutoff', unicontent(r)) session.meeting.date = datetime.date.today() session.meeting.save() # Invalid order r = self.client.post(url, {}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('No data',r.json()['error']) r = self.client.post(url, {'garbage':'garbage'}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('order is not valid',r.json()['error']) r = self.client.post(url, {'order':0, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('order is not valid',r.json()['error']) r = self.client.post(url, {'order':2, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('order is not valid',r.json()['error']) r = self.client.post(url, {'order':'garbage', 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('order is not valid',r.json()['error']) # Invalid name r = self.client.post(url, {'order':1 }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('name is not valid',r.json()['error']) r = self.client.post(url, {'order':1, 'name':'garbage' }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('name is not valid',r.json()['error']) # Valid post r = self.client.post(url, {'order':1, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(session.sessionpresentation_set.count(),1) # Ingore a request to add slides that are already in a session r = self.client.post(url, {'order':1, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(session.sessionpresentation_set.count(),1) session2 = SessionFactory(group=session.group, meeting=session.meeting) SessionPresentationFactory.create_batch(3, document__type_id='slides', session=session2) for num, sp in enumerate(session2.sessionpresentation_set.filter(document__type_id='slides'),start=1): sp.order = num sp.save() url = urlreverse('ietf.meeting.views.ajax_add_slides_to_session', kwargs={'session_id':session2.pk, 'num':session2.meeting.number}) more_slides = DocumentFactory.create_batch(3, type_id='slides') # Insert at beginning r = self.client.post(url, {'order':1, 'name':more_slides[0].name}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(session2.sessionpresentation_set.get(document=more_slides[0]).order,1) self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), list(range(1,5))) # Insert at end r = self.client.post(url, {'order':5, 'name':more_slides[1].name}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(session2.sessionpresentation_set.get(document=more_slides[1]).order,5) self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), list(range(1,6))) # Insert in middle r = self.client.post(url, {'order':3, 'name':more_slides[2].name}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(session2.sessionpresentation_set.get(document=more_slides[2]).order,3) self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), list(range(1,7))) def test_remove_slides_from_session(self): for type_id in ['ietf','interim']: chair_role = RoleFactory(name_id='chair') session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90), meeting__type_id=type_id) slides = DocumentFactory(type_id='slides') url = urlreverse('ietf.meeting.views.ajax_remove_slides_from_session', kwargs={'session_id':session.pk, 'num':session.meeting.number}) # Not a valid user r = self.client.post(url, {'oldIndex':1, 'name':slides.name }) self.assertEqual(r.status_code, 403) self.assertIn('have permission', unicontent(r)) self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password") # Past submission cutoff r = self.client.post(url, {'oldIndex':0, 'name':slides.name }) self.assertEqual(r.status_code, 403) self.assertIn('materials cutoff', unicontent(r)) session.meeting.date = datetime.date.today() session.meeting.save() # Invalid order r = self.client.post(url, {}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('No data',r.json()['error']) r = self.client.post(url, {'garbage':'garbage'}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) r = self.client.post(url, {'oldIndex':0, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) r = self.client.post(url, {'oldIndex':'garbage', 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) # No matching thing to delete r = self.client.post(url, {'oldIndex':1, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) session.sessionpresentation_set.create(document=slides, rev=slides.rev, order=1) # Bad names r = self.client.post(url, {'oldIndex':1}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('name is not valid',r.json()['error']) r = self.client.post(url, {'oldIndex':1, 'name':'garbage' }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('name is not valid',r.json()['error']) slides2 = DocumentFactory(type_id='slides') # index/name mismatch r = self.client.post(url, {'oldIndex':1, 'name':slides2.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('SessionPresentation not found',r.json()['error']) session.sessionpresentation_set.create(document=slides2, rev=slides2.rev, order=2) r = self.client.post(url, {'oldIndex':1, 'name':slides2.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('Name does not match index',r.json()['error']) # valid removal r = self.client.post(url, {'oldIndex':1, 'name':slides.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(session.sessionpresentation_set.count(),1) session2 = SessionFactory(group=session.group, meeting=session.meeting) sp_list = SessionPresentationFactory.create_batch(5, document__type_id='slides', session=session2) for num, sp in enumerate(session2.sessionpresentation_set.filter(document__type_id='slides'),start=1): sp.order = num sp.save() url = urlreverse('ietf.meeting.views.ajax_remove_slides_from_session', kwargs={'session_id':session2.pk, 'num':session2.meeting.number}) # delete at first of list r = self.client.post(url, {'oldIndex':1, 'name':sp_list[0].document.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertFalse(session2.sessionpresentation_set.filter(pk=sp_list[0].pk).exists()) self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), list(range(1,5))) # delete in middle of list r = self.client.post(url, {'oldIndex':4, 'name':sp_list[4].document.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertFalse(session2.sessionpresentation_set.filter(pk=sp_list[4].pk).exists()) self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), list(range(1,4))) # delete at end of list r = self.client.post(url, {'oldIndex':2, 'name':sp_list[2].document.name }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertFalse(session2.sessionpresentation_set.filter(pk=sp_list[2].pk).exists()) self.assertEqual(list(session2.sessionpresentation_set.order_by('order').values_list('order',flat=True)), list(range(1,3))) def test_reorder_slides_in_session(self): chair_role = RoleFactory(name_id='chair') session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90)) sp_list = SessionPresentationFactory.create_batch(5, document__type_id='slides', session=session) for num, sp in enumerate(sp_list, start=1): sp.order = num sp.save() url = urlreverse('ietf.meeting.views.ajax_reorder_slides_in_session', kwargs={'session_id':session.pk, 'num':session.meeting.number}) for type_id in ['ietf','interim']: session.meeting.type_id = type_id session.meeting.date = datetime.date.today()-datetime.timedelta(days=90) session.meeting.save() # Not a valid user r = self.client.post(url, {'oldIndex':1, 'newIndex':2 }) self.assertEqual(r.status_code, 403) self.assertIn('have permission', unicontent(r)) self.client.login(username=chair_role.person.user.username, password=chair_role.person.user.username+"+password") # Past submission cutoff r = self.client.post(url, {'oldIndex':1, 'newIndex':2 }) self.assertEqual(r.status_code, 403) self.assertIn('materials cutoff', unicontent(r)) session.meeting.date = datetime.date.today() session.meeting.save() # Bad index values r = self.client.post(url, {'oldIndex':0, 'newIndex':2 }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) r = self.client.post(url, {'oldIndex':2, 'newIndex':6 }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) r = self.client.post(url, {'oldIndex':2, 'newIndex':2 }) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],False) self.assertIn('index is not valid',r.json()['error']) # Move from beginning r = self.client.post(url, {'oldIndex':1, 'newIndex':3}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,3,1,4,5]) # Move to beginning r = self.client.post(url, {'oldIndex':3, 'newIndex':1}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[1,2,3,4,5]) # Move from end r = self.client.post(url, {'oldIndex':5, 'newIndex':3}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[1,2,5,3,4]) # Move to end r = self.client.post(url, {'oldIndex':3, 'newIndex':5}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[1,2,3,4,5]) # Move beginning to end r = self.client.post(url, {'oldIndex':1, 'newIndex':5}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,3,4,5,1]) # Move middle to middle r = self.client.post(url, {'oldIndex':3, 'newIndex':4}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,3,5,4,1]) r = self.client.post(url, {'oldIndex':3, 'newIndex':2}) self.assertEqual(r.status_code, 200) self.assertEqual(r.json()['success'],True) self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('pk',flat=True)),[2,5,3,4,1]) # Reset for next iteration in the loop session.sessionpresentation_set.update(order=F('pk')) self.client.logout() def test_slide_order_reconditioning(self): chair_role = RoleFactory(name_id='chair') session = SessionFactory(group=chair_role.group, meeting__date=datetime.date.today()-datetime.timedelta(days=90)) sp_list = SessionPresentationFactory.create_batch(5, document__type_id='slides', session=session) for num, sp in enumerate(sp_list, start=1): sp.order = 2*num sp.save() try: condition_slide_order(session) except AssertionError: pass self.assertEqual(list(session.sessionpresentation_set.order_by('order').values_list('order',flat=True)),list(range(1,6))) class EditTests(TestCase): def setUp(self): # make sure we have the colors of the area from ietf.group.colors import fg_group_colors, bg_group_colors area_upper = "FARFUT" fg_group_colors[area_upper] = "#333" bg_group_colors[area_upper] = "#aaa" def test_edit_schedule(self): meeting = make_meeting_test_data() self.client.login(username="secretary", password="secretary+password") r = self.client.get(urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number))) self.assertContains(r, "load_assignments") def test_edit_meeting_schedule(self): meeting = make_meeting_test_data() self.client.login(username="secretary", password="secretary+password") s1 = Session.objects.filter(meeting=meeting, type='regular').first() s2 = Session.objects.filter(meeting=meeting, type='regular').exclude(group=s1.group).first() s1.comments = "Hello world!" s1.attendees = 1234 s1.save() Constraint.objects.create( meeting=meeting, source=s1.group, target=s2.group, name=ConstraintName.objects.get(slug="conflict"), ) p = Person.objects.order_by('pk')[1] Constraint.objects.create( meeting=meeting, source=s1.group, person=p, name=ConstraintName.objects.get(slug="bethere"), ) Constraint.objects.create( meeting=meeting, source=s2.group, person=p, name=ConstraintName.objects.get(slug="bethere"), ) room = Room.objects.get(meeting=meeting, session_types='regular') base_timeslot = TimeSlot.objects.create(meeting=meeting, type_id='regular', location=room, duration=datetime.timedelta(minutes=50), time=datetime.datetime.combine(meeting.date + datetime.timedelta(days=2), datetime.time(9, 30))) timeslots = list(TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('time')) base_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="irg"), attendees=20, requested_duration=datetime.timedelta(minutes=30), type_id='regular') SchedulingEvent.objects.create(session=base_session, status_id='schedw', by=Person.objects.get(user__username='secretary')) SchedTimeSessAssignment.objects.create(timeslot=base_timeslot, session=base_session, schedule=meeting.schedule.base) # check we have the grid and everything set up as a baseline - # the Javascript tests check that the Javascript can work with # it url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number)) r = self.client.get(url) q = PyQuery(r.content) self.assertTrue(q(".room-name:contains(\"{}\")".format(room.name))) self.assertTrue(q(".room-name:contains(\"{}\")".format(room.capacity))) self.assertTrue(q("#timeslot{}".format(timeslots[0].pk))) for s in [s1, s2]: e = q("#session{}".format(s.pk)) # info in the item representing the session that can be moved around self.assertIn(s.group.acronym, e.find(".session-label").text()) if s.comments: self.assertTrue(e.find(".comments")) if s.attendees is not None: self.assertIn(str(s.attendees), e.find(".attendees").text()) self.assertTrue(e.hasClass("parent-{}".format(s.group.parent.acronym))) constraints = e.find(".constraints > span") s_other = s2 if s == s1 else s1 self.assertEqual(len(constraints), 3) self.assertEqual(constraints.eq(0).attr("data-sessions"), str(s_other.pk)) self.assertEqual(constraints.eq(0).find(".fa-user-o").parent().text(), "1") # 1 person in the constraint self.assertEqual(constraints.eq(1).attr("data-sessions"), str(s_other.pk)) self.assertEqual(constraints.eq(1).find(".encircled").text(), "1" if s_other == s2 else "-1") self.assertEqual(constraints.eq(2).attr("data-sessions"), str(s_other.pk)) self.assertEqual(constraints.eq(2).find(".encircled").text(), "AD") # session info for the panel self.assertIn(str(round(s.requested_duration.total_seconds() / 60.0 / 60, 1)), e.find(".session-info .title").text()) event = SchedulingEvent.objects.filter(session=s).order_by("id").first() if event: self.assertTrue(e.find("div:contains(\"{}\")".format(event.by.plain_name()))) if s.comments: self.assertIn(s.comments, e.find(".comments").text()) formatted_constraints1 = q("#session{} .session-info .formatted-constraints > *".format(s1.pk)) self.assertIn(s2.group.acronym, formatted_constraints1.eq(0).html()) self.assertIn(p.name, formatted_constraints1.eq(1).html()) formatted_constraints2 = q("#session{} .session-info .formatted-constraints > *".format(s2.pk)) self.assertIn(p.name, formatted_constraints2.eq(0).html()) self.assertEqual(len(q("#session{}.readonly".format(base_session.pk))), 1) self.assertTrue(q("em:contains(\"You can't edit this schedule\")")) # can't change anything r = self.client.post(url, { 'action': 'assign', 'timeslot': timeslots[0].pk, 'session': s1.pk, }) self.assertEqual(r.status_code, 403) # turn us into owner schedule = meeting.schedule schedule.owner = Person.objects.get(user__username="secretary") schedule.save() meeting.schedule = None meeting.save() url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number, owner=schedule.owner_email(), name=schedule.name)) r = self.client.get(url) q = PyQuery(r.content) self.assertTrue(not q("em:contains(\"You can't edit this schedule\")")) SchedTimeSessAssignment.objects.filter(session=s1).delete() # assign r = self.client.post(url, { 'action': 'assign', 'timeslot': timeslots[0].pk, 'session': s1.pk, }) self.assertEqual(json.loads(r.content)['success'], True) self.assertEqual(SchedTimeSessAssignment.objects.get(schedule=schedule, session=s1).timeslot, timeslots[0]) # move assignment on unofficial schedule r = self.client.post(url, { 'action': 'assign', 'timeslot': timeslots[1].pk, 'session': s1.pk, }) self.assertEqual(json.loads(r.content)['success'], True) self.assertEqual(SchedTimeSessAssignment.objects.get(schedule=schedule, session=s1).timeslot, timeslots[1]) # move assignment on official schedule, leaving tombstone meeting.schedule = schedule meeting.save() SchedulingEvent.objects.create( session=s1, status=SessionStatusName.objects.get(slug='sched'), by=Person.objects.get(name='(System)') ) r = self.client.post(url, { 'action': 'assign', 'timeslot': timeslots[0].pk, 'session': s1.pk, }) json_content = json.loads(r.content) self.assertEqual(json_content['success'], True) self.assertEqual(SchedTimeSessAssignment.objects.get(schedule=schedule, session=s1).timeslot, timeslots[0]) sessions_for_group = Session.objects.filter(group=s1.group, meeting=meeting) self.assertEqual(len(sessions_for_group), 2) s_tombstone = [s for s in sessions_for_group if s != s1][0] self.assertEqual(s_tombstone.tombstone_for, s1) tombstone_event = SchedulingEvent.objects.get(session=s_tombstone) self.assertEqual(tombstone_event.status_id, 'resched') self.assertEqual(SchedTimeSessAssignment.objects.get(schedule=schedule, session=s_tombstone).timeslot, timeslots[1]) self.assertTrue(PyQuery(json_content['tombstone'])("#session{}.readonly".format(s_tombstone.pk)).html()) # unassign r = self.client.post(url, { 'action': 'unassign', 'session': s1.pk, }) self.assertEqual(json.loads(r.content)['success'], True) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1)), []) # try swapping days SchedTimeSessAssignment.objects.create(schedule=schedule, session=s1, timeslot=timeslots[0]) self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[0])), 1) self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2, timeslot=timeslots[1])), 1) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[2])), []) r = self.client.post(url, { 'action': 'swapdays', 'source_day': timeslots[0].time.date().isoformat(), 'target_day': timeslots[2].time.date().isoformat(), }) self.assertEqual(r.status_code, 302) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[0])), []) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[1])), []) self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[2])), 1) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s2)), []) # swap back r = self.client.post(url, { 'action': 'swapdays', 'source_day': timeslots[2].time.date().isoformat(), 'target_day': timeslots[0].time.date().isoformat(), }) self.assertEqual(r.status_code, 302) self.assertEqual(len(SchedTimeSessAssignment.objects.filter(schedule=schedule, session=s1, timeslot=timeslots[0])), 1) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[1])), []) self.assertEqual(list(SchedTimeSessAssignment.objects.filter(schedule=schedule, timeslot=timeslots[2])), []) def test_edit_meeting_timeslots_and_misc_sessions(self): meeting = make_meeting_test_data() self.client.login(username="secretary", password="secretary+password") # check we have the grid and everything set up as a baseline - # the Javascript tests check that the Javascript can work with # it url = urlreverse("ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions", kwargs=dict(num=meeting.number, owner=meeting.schedule.base.owner_email(), name=meeting.schedule.base.name)) r = self.client.get(url) q = PyQuery(r.content) breakfast_room = Room.objects.get(meeting=meeting, name="Breakfast Room") break_room = Room.objects.get(meeting=meeting, name="Break Area") reg_room = Room.objects.get(meeting=meeting, name="Registration Area") for i in range(meeting.days): self.assertTrue(q("[data-day=\"{}\"]".format((meeting.date + datetime.timedelta(days=i)).isoformat()))) self.assertTrue(q(".room-label:contains(\"{}\")".format(breakfast_room.name))) self.assertTrue(q(".room-label:contains(\"{}\")".format(break_room.name))) self.assertTrue(q(".room-label:contains(\"{}\")".format(reg_room.name))) break_slot = TimeSlot.objects.get(location=break_room, type='break') room_row = q(".room-row[data-day=\"{}\"][data-room=\"{}\"]".format(break_slot.time.date().isoformat(), break_slot.location_id)) self.assertTrue(room_row) self.assertTrue(room_row.find("#timeslot{}".format(break_slot.pk))) self.assertTrue(q(".timeslot-form")) # add timeslot ietf_group = Group.objects.get(acronym='ietf') r = self.client.post(url, { 'day': meeting.date, 'time': '08:30', 'duration': '1:30', 'location': break_room.pk, 'show_location': 'on', 'type': 'other', 'group': ietf_group.pk, 'name': "IETF Testing", 'short': "ietf-testing", 'scroll': 1234, 'action': 'add-timeslot', }) self.assertNoFormPostErrors(r) self.assertIn("#scroll=1234", r['Location']) test_timeslot = TimeSlot.objects.get(meeting=meeting, name="IETF Testing") self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(8, 30))) self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1, minutes=30)) self.assertEqual(test_timeslot.location_id, break_room.pk) self.assertEqual(test_timeslot.show_location, True) self.assertEqual(test_timeslot.type_id, 'other') test_session = Session.objects.get(meeting=meeting, timeslotassignments__timeslot=test_timeslot) self.assertEqual(test_session.short, 'ietf-testing') self.assertEqual(test_session.group, ietf_group) self.assertTrue(SchedulingEvent.objects.filter(session=test_session, status='sched')) # edit timeslot r = self.client.get(url, { 'timeslot': test_timeslot.pk, 'action': 'edit-timeslot', }) self.assertEqual(r.status_code, 200) edit_form_html = json.loads(r.content)['form'] q = PyQuery(edit_form_html) self.assertEqual(q("[name=name]").val(), test_timeslot.name) self.assertEqual(q("[name=location]").val(), str(test_timeslot.location_id)) self.assertEqual(q("[name=timeslot]").val(), str(test_timeslot.pk)) self.assertEqual(q("[name=type]").val(), str(test_timeslot.type_id)) self.assertEqual(q("[name=group]").val(), str(ietf_group.pk)) iab_group = Group.objects.get(acronym='iab') r = self.client.post(url, { 'timeslot': test_timeslot.pk, 'day': meeting.date, 'time': '09:30', 'duration': '1:00', 'location': breakfast_room.pk, 'type': 'other', 'group': iab_group.pk, 'name': "IETF Testing 2", 'short': "ietf-testing2", 'action': 'edit-timeslot', }) self.assertNoFormPostErrors(r) test_timeslot.refresh_from_db() self.assertEqual(test_timeslot.time, datetime.datetime.combine(meeting.date, datetime.time(9, 30))) self.assertEqual(test_timeslot.duration, datetime.timedelta(hours=1)) self.assertEqual(test_timeslot.location_id, breakfast_room.pk) self.assertEqual(test_timeslot.show_location, False) self.assertEqual(test_timeslot.type_id, 'other') test_session.refresh_from_db() self.assertEqual(test_session.short, 'ietf-testing2') self.assertEqual(test_session.group, iab_group) # cancel timeslot r = self.client.post(url, { 'timeslot': test_timeslot.pk, 'action': 'cancel-timeslot', }) self.assertNoFormPostErrors(r) event = SchedulingEvent.objects.filter( session__timeslotassignments__timeslot=test_timeslot ).order_by('-id').first() self.assertEqual(event.status_id, 'canceled') # delete timeslot test_presentation = Document.objects.create(name='slides-test', type_id='slides') SessionPresentation.objects.create( document=test_presentation, rev='1', session=test_session ) r = self.client.post(url, { 'timeslot': test_timeslot.pk, 'action': 'delete-timeslot', }) self.assertNoFormPostErrors(r) self.assertEqual(list(TimeSlot.objects.filter(pk=test_timeslot.pk)), []) self.assertEqual(list(Session.objects.filter(pk=test_session.pk)), []) self.assertEqual(test_presentation.get_state_slug(), 'deleted') # set agenda note assignment = SchedTimeSessAssignment.objects.filter(session__group__acronym='mars', schedule=meeting.schedule).first() url = urlreverse("ietf.meeting.views.edit_meeting_timeslots_and_misc_sessions", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) r = self.client.post(url, { 'timeslot': assignment.timeslot_id, 'day': assignment.timeslot.time.date().isoformat(), 'time': assignment.timeslot.time.time().isoformat(), 'duration': assignment.timeslot.duration, 'location': assignment.timeslot.location_id, 'type': assignment.timeslot.type_id, 'name': assignment.timeslot.name, 'agenda_note': "New Test Note", 'action': 'edit-timeslot', }) self.assertNoFormPostErrors(r) assignment.session.refresh_from_db() self.assertEqual(assignment.session.agenda_note, "New Test Note") def test_new_meeting_schedule(self): meeting = make_meeting_test_data() self.client.login(username="secretary", password="secretary+password") # new from scratch url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code, 200) r = self.client.post(url, { 'name': "scratch", 'public': "on", 'visible': "on", 'notes': "New scratch", 'base': meeting.schedule.base_id, }) self.assertNoFormPostErrors(r) new_schedule = Schedule.objects.get(meeting=meeting, owner__user__username='secretary', name='scratch') self.assertEqual(new_schedule.public, True) self.assertEqual(new_schedule.visible, True) self.assertEqual(new_schedule.notes, "New scratch") self.assertEqual(new_schedule.origin, None) self.assertEqual(new_schedule.base_id, meeting.schedule.base_id) # copy url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) r = self.client.get(url) self.assertEqual(r.status_code, 200) r = self.client.post(url, { 'name': "copy", 'public': "on", 'notes': "New copy", 'base': meeting.schedule.base_id, }) self.assertNoFormPostErrors(r) new_schedule = Schedule.objects.get(meeting=meeting, owner__user__username='secretary', name='copy') self.assertEqual(new_schedule.public, True) self.assertEqual(new_schedule.visible, False) self.assertEqual(new_schedule.notes, "New copy") self.assertEqual(new_schedule.origin, meeting.schedule) self.assertEqual(new_schedule.base_id, meeting.schedule.base_id) old_assignments = {(a.session_id, a.timeslot_id) for a in SchedTimeSessAssignment.objects.filter(schedule=meeting.schedule)} for a in SchedTimeSessAssignment.objects.filter(schedule=new_schedule): self.assertIn((a.session_id, a.timeslot_id), old_assignments) def test_save_agenda_as_and_read_permissions(self): meeting = make_meeting_test_data() # try to get non-existing agenda url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name="foo")) r = self.client.get(url) self.assertEqual(r.status_code, 404) # save as new name (requires valid existing agenda) url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) self.client.login(username="ad", password="ad+password") r = self.client.post(url, { 'savename': "foo", 'saveas': "saveas", }) self.assertEqual(r.status_code, 302) # Verify that we actually got redirected to a new place. self.assertNotEqual(urlparse(r.url).path, url) # get schedule = meeting.get_schedule_by_name("foo") url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number, owner=schedule.owner_email(), name="foo")) r = self.client.get(url) self.assertEqual(r.status_code, 200) schedule.visible = True schedule.public = False schedule.save() # get as anonymous doesn't work self.client.logout() r = self.client.get(url) self.assertEqual(r.status_code, 403) # public, now anonymous works schedule.public = True schedule.save() r = self.client.get(url) self.assertEqual(r.status_code, 200) # Secretariat can always see it schedule.visible = False schedule.public = False schedule.save() self.client.login(username="secretary", password="secretary+password") r = self.client.get(url) self.assertEqual(r.status_code, 200) def test_save_agenda_broken_names(self): meeting = make_meeting_test_data() # save as new name (requires valid existing agenda) url = urlreverse("ietf.meeting.views.edit_schedule", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) self.client.login(username="ad", password="ad+password") r = self.client.post(url, { 'savename': "/no/this/should/not/work/it/is/too/long", 'saveas': "saveas", }) self.assertEqual(r.status_code, 302) self.assertEqual(urlparse(r.url).path, url) # TODO: Verify that an error message was in fact returned. r = self.client.post(url, { 'savename': "/invalid/chars/", 'saveas': "saveas", }) # TODO: Verify that an error message was in fact returned. self.assertEqual(r.status_code, 302) self.assertEqual(urlparse(r.url).path, url) # Non-ASCII alphanumeric characters r = self.client.post(url, { 'savename': "f\u00E9ling", 'saveas': "saveas", }) # TODO: Verify that an error message was in fact returned. self.assertEqual(r.status_code, 302) self.assertEqual(urlparse(r.url).path, url) def test_edit_timeslots(self): meeting = make_meeting_test_data() self.client.login(username="secretary", password="secretary+password") r = self.client.get(urlreverse("ietf.meeting.views.edit_timeslots", kwargs=dict(num=meeting.number))) self.assertContains(r, meeting.room_set.all().first().name) def test_edit_timeslot_type(self): timeslot = TimeSlotFactory(meeting__type_id='ietf') url = urlreverse('ietf.meeting.views.edit_timeslot_type', kwargs=dict(num=timeslot.meeting.number,slot_id=timeslot.id)) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) r = self.client.post(url,{'type':'other',}) self.assertEqual(r.status_code, 302) timeslot = TimeSlot.objects.get(id=timeslot.id) self.assertEqual(timeslot.type.slug,'other') def test_slot_to_the_right(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() mars_scheduled = session.timeslotassignments.get(schedule__name='test-schedule') mars_slot = TimeSlot.objects.get(sessionassignments__session=session,sessionassignments__schedule__name='test-schedule') mars_ends = mars_slot.time + mars_slot.duration session = Session.objects.filter(meeting=meeting, group__acronym="ames").first() ames_slot_qs = TimeSlot.objects.filter(sessionassignments__session=session,sessionassignments__schedule__name='test-schedule') ames_slot_qs.update(time=mars_ends + datetime.timedelta(seconds=11 * 60)) self.assertTrue(not mars_slot.slot_to_the_right) self.assertTrue(not mars_scheduled.slot_to_the_right) ames_slot_qs.update(time=mars_ends + datetime.timedelta(seconds=10 * 60)) self.assertTrue(mars_slot.slot_to_the_right) self.assertTrue(mars_scheduled.slot_to_the_right) class SessionDetailsTests(TestCase): def test_session_details(self): group = GroupFactory.create(type_id='wg',state_id='active') session = SessionFactory.create(meeting__type_id='ietf',group=group, meeting__date=datetime.date.today()+datetime.timedelta(days=90)) SessionPresentationFactory.create(session=session,document__type_id='draft',rev=None) SessionPresentationFactory.create(session=session,document__type_id='minutes') SessionPresentationFactory.create(session=session,document__type_id='slides') SessionPresentationFactory.create(session=session,document__type_id='agenda') url = urlreverse('ietf.meeting.views.session_details', kwargs=dict(num=session.meeting.number, acronym=group.acronym)) r = self.client.get(url) self.assertTrue(all([x in unicontent(r) for x in ('slides','agenda','minutes','draft')])) self.assertNotContains(r, 'deleted') q = PyQuery(r.content) self.assertTrue(q('h2#session_%s span#session-buttons-%s' % (session.id, session.id)), 'Session detail page does not contain session tool buttons') self.assertFalse(q('h2#session_%s div#session-buttons-%s span.fa-arrows-alt' % (session.id, session.id)), 'The session detail page is incorrectly showing the "Show meeting materials" button') def test_session_details_past_interim(self): group = GroupFactory.create(type_id='wg',state_id='active') chair = RoleFactory(name_id='chair',group=group) session = SessionFactory.create(meeting__type_id='interim',group=group, meeting__date=datetime.date.today()-datetime.timedelta(days=90)) SessionPresentationFactory.create(session=session,document__type_id='draft',rev=None) SessionPresentationFactory.create(session=session,document__type_id='minutes') SessionPresentationFactory.create(session=session,document__type_id='slides') SessionPresentationFactory.create(session=session,document__type_id='agenda') url = urlreverse('ietf.meeting.views.session_details', kwargs=dict(num=session.meeting.number, acronym=group.acronym)) r = self.client.get(url) self.assertEqual(r.status_code,200) self.assertNotIn('The materials upload cutoff date for this session has passed', unicontent(r)) r = self.client.get(url) self.assertEqual(r.status_code,200) self.client.login(username=chair.person.user.username,password=chair.person.user.username+'+password') self.assertTrue(all([x in unicontent(r) for x in ('slides','agenda','minutes','draft')])) def test_add_session_drafts(self): group = GroupFactory.create(type_id='wg',state_id='active') group_chair = PersonFactory.create() group.role_set.create(name_id='chair',person = group_chair, email = group_chair.email()) session = SessionFactory.create(meeting__type_id='ietf',group=group, meeting__date=datetime.date.today()+datetime.timedelta(days=90)) SessionPresentationFactory.create(session=session,document__type_id='draft',rev=None) old_draft = session.sessionpresentation_set.filter(document__type='draft').first().document new_draft = DocumentFactory(type_id='draft') url = urlreverse('ietf.meeting.views.add_session_drafts', kwargs=dict(num=session.meeting.number, session_id=session.pk)) r = self.client.get(url) self.assertEqual(r.status_code, 404) self.client.login(username="plain",password="plain+password") r = self.client.get(url) self.assertEqual(r.status_code, 404) self.client.login(username=group_chair.user.username, password='%s+password'%group_chair.user.username) r = self.client.get(url) self.assertContains(r, old_draft.name) r = self.client.post(url,dict(drafts=[new_draft.pk, old_draft.pk])) self.assertTrue(r.status_code, 200) q = PyQuery(r.content) self.assertIn("Already linked:", q('form .alert-danger').text()) self.assertEqual(1,session.sessionpresentation_set.count()) r = self.client.post(url,dict(drafts=[new_draft.pk,])) self.assertTrue(r.status_code, 302) self.assertEqual(2,session.sessionpresentation_set.count()) session.meeting.date -= datetime.timedelta(days=180) session.meeting.save() r = self.client.get(url) self.assertEqual(r.status_code,404) self.client.login(username='secretary',password='secretary+password') r = self.client.get(url) self.assertEqual(r.status_code,200) q = PyQuery(r.content) self.assertEqual(1,len(q(".alert-warning:contains('may affect published proceedings')"))) class EditScheduleListTests(TestCase): def setUp(self): self.mtg = MeetingFactory(type_id='ietf') ScheduleFactory(meeting=self.mtg, name='secretary1') def test_list_schedules(self): url = urlreverse('ietf.meeting.views.list_schedules',kwargs={'num':self.mtg.number}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertTrue(r.status_code, 200) def test_diff_schedules(self): meeting = make_meeting_test_data() url = urlreverse('ietf.meeting.views.diff_schedules',kwargs={'num':meeting.number}) login_testing_unauthorized(self,"secretary", url) r = self.client.get(url) self.assertTrue(r.status_code, 200) from_schedule = Schedule.objects.get(meeting=meeting, name="test-unofficial-schedule") session1 = Session.objects.filter(meeting=meeting, group__acronym='mars').first() session2 = Session.objects.filter(meeting=meeting, group__acronym='ames').first() session3 = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym='mars'), attendees=10, requested_duration=datetime.timedelta(minutes=70), type_id='regular') SchedulingEvent.objects.create(session=session3, status_id='schedw', by=Person.objects.first()) slot2 = TimeSlot.objects.filter(meeting=meeting, type='regular').order_by('-time').first() slot3 = TimeSlot.objects.create( meeting=meeting, type_id='regular', location=slot2.location, duration=datetime.timedelta(minutes=60), time=slot2.time + datetime.timedelta(minutes=60), ) # copy new_url = urlreverse("ietf.meeting.views.new_meeting_schedule", kwargs=dict(num=meeting.number, owner=from_schedule.owner_email(), name=from_schedule.name)) r = self.client.post(new_url, { 'name': "newtest", 'public': "on", }) self.assertNoFormPostErrors(r) to_schedule = Schedule.objects.get(meeting=meeting, name='newtest') # make some changes edit_url = urlreverse("ietf.meeting.views.edit_meeting_schedule", kwargs=dict(num=meeting.number, owner=to_schedule.owner_email(), name=to_schedule.name)) # schedule session r = self.client.post(edit_url, { 'action': 'assign', 'timeslot': slot3.pk, 'session': session3.pk, }) self.assertEqual(json.loads(r.content)['success'], True) # unschedule session r = self.client.post(edit_url, { 'action': 'unassign', 'session': session1.pk, }) self.assertEqual(json.loads(r.content)['success'], True) # move session r = self.client.post(edit_url, { 'action': 'assign', 'timeslot': slot2.pk, 'session': session2.pk, }) self.assertEqual(json.loads(r.content)['success'], True) # now get differences r = self.client.get(url, { 'from_schedule': from_schedule.name, 'to_schedule': to_schedule.name, }) self.assertTrue(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q(".schedule-diffs tr")), 3) def test_delete_schedule(self): url = urlreverse('ietf.meeting.views.delete_schedule', kwargs={'num':self.mtg.number, 'owner':self.mtg.schedule.owner.email_address(), 'name':self.mtg.schedule.name, }) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertTrue(r.status_code, 403) r = self.client.post(url,{'save':1}) self.assertTrue(r.status_code, 403) self.assertEqual(self.mtg.schedule_set.count(),2) self.mtg.schedule=None self.mtg.save() r = self.client.get(url) self.assertTrue(r.status_code, 200) r = self.client.post(url,{'save':1}) self.assertTrue(r.status_code, 302) self.assertEqual(self.mtg.schedule_set.count(),1) def test_make_schedule_official(self): schedule = self.mtg.schedule_set.exclude(id=self.mtg.schedule.id).first() url = urlreverse('ietf.meeting.views.make_schedule_official', kwargs={'num':self.mtg.number, 'owner':schedule.owner.email_address(), 'name':schedule.name, }) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertTrue(r.status_code, 200) r = self.client.post(url,{'save':1}) self.assertTrue(r.status_code, 302) mtg = Meeting.objects.get(number=self.mtg.number) self.assertEqual(mtg.schedule,schedule) # ------------------------------------------------- # Interim Meeting Tests # ------------------------------------------------- class InterimTests(TestCase): def setUp(self): self.materials_dir = self.tempdir('materials') self.saved_agenda_path = settings.AGENDA_PATH settings.AGENDA_PATH = self.materials_dir def tearDown(self): settings.AGENDA_PATH = self.saved_agenda_path shutil.rmtree(self.materials_dir) # test_interim_announce subsumed by test_appears_on_announce def do_interim_skip_announcement_test(self, base_session=False, extra_session=False, canceled_session=False): make_meeting_test_data() group = Group.objects.get(acronym='irg') date = datetime.date.today() + datetime.timedelta(days=30) meeting = make_interim_meeting(group=group, date=date, status='scheda') session = meeting.session_set.first() if base_session: base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False) meeting.schedule.base = Schedule.objects.create( meeting=meeting, owner=PersonFactory(), name="base", visible=True, public=True ) SchedTimeSessAssignment.objects.create( timeslot=TimeSlotFactory.create(meeting=meeting), session=base_session, schedule=meeting.schedule.base, ) meeting.schedule.save() if extra_session: extra_session = SessionFactory(meeting=meeting, status_id='scheda') if canceled_session: canceled_session = SessionFactory(meeting=meeting, status_id='canceledpa') url = urlreverse("ietf.meeting.views.interim_skip_announcement", kwargs={'number': meeting.number}) login_testing_unauthorized(self, "secretary", url) r = self.client.get(url) self.assertEqual(r.status_code, 200) # check post len_before = len(outbox) r = self.client.post(url) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_announce')) meeting_sessions = meeting.session_set.with_current_status() self.assertEqual(meeting_sessions.get(pk=session.pk).current_status, 'sched') if base_session: self.assertEqual(meeting_sessions.get(pk=base_session.pk).current_status, 'sched') if extra_session: self.assertEqual(meeting_sessions.get(pk=extra_session.pk).current_status, 'sched') if canceled_session: self.assertEqual(meeting_sessions.get(pk=canceled_session.pk).current_status, 'canceledpa') self.assertEqual(len(outbox), len_before) def test_interim_skip_announcement(self): """skip_announcement should move single session to sched state""" self.do_interim_skip_announcement_test() def test_interim_skip_announcement_with_base_sched(self): """skip_announcement should move single session to sched state""" self.do_interim_skip_announcement_test(base_session=True) def test_interim_skip_announcement_with_extra_session(self): """skip_announcement should move multiple sessions to sched state""" self.do_interim_skip_announcement_test(extra_session=True) def test_interim_skip_announcement_with_extra_session_and_base_sched(self): """skip_announcement should move multiple sessions to sched state""" self.do_interim_skip_announcement_test(extra_session=True, base_session=True) def test_interim_skip_announcement_with_canceled_session(self): """skip_announcement should not schedule a canceled session""" self.do_interim_skip_announcement_test(canceled_session=True) def test_interim_skip_announcement_with_canceled_session_and_base_sched(self): """skip_announcement should not schedule a canceled session""" self.do_interim_skip_announcement_test(canceled_session=True, base_session=True) def test_interim_skip_announcement_with_extra_and_canceled_sessions(self): """skip_announcement should schedule multiple sessions and leave canceled session alone""" self.do_interim_skip_announcement_test(extra_session=True, canceled_session=True) def test_interim_skip_announcement_with_extra_and_canceled_sessions_and_base_sched(self): """skip_announcement should schedule multiple sessions and leave canceled session alone""" self.do_interim_skip_announcement_test(extra_session=True, canceled_session=True, base_session=True) def do_interim_send_announcement_test(self, base_session=False, extra_session=False, canceled_session=False): make_interim_test_data() session = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='apprw').first() meeting = session.meeting meeting.time_zone = 'America/Los_Angeles' meeting.save() if base_session: base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False) meeting.schedule.base = Schedule.objects.create( meeting=meeting, owner=PersonFactory(), name="base", visible=True, public=True ) SchedTimeSessAssignment.objects.create( timeslot=TimeSlotFactory.create(meeting=meeting), session=base_session, schedule=meeting.schedule.base, ) meeting.schedule.save() if extra_session: extra_session = SessionFactory(meeting=meeting, status_id='apprw') if canceled_session: canceled_session = SessionFactory(meeting=meeting, status_id='canceledpa') url = urlreverse("ietf.meeting.views.interim_send_announcement", kwargs={'number': meeting.number}) login_testing_unauthorized(self, "secretary", url) r = self.client.get(url) self.assertEqual(r.status_code, 200) initial = r.context['form'].initial # send announcement len_before = len(outbox) r = self.client.post(url, initial) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_announce')) self.assertEqual(len(outbox), len_before + 1) announcement_msg = outbox[-1] announcement_text = get_payload_text(announcement_msg) self.assertIn('WG Virtual Meeting', announcement_msg['Subject']) self.assertIn('09:00 to 09:20 America/Los_Angeles', announcement_text) for sess in [session, base_session, extra_session]: if sess: timeslot = sess.official_timeslotassignment().timeslot self.assertIn(timeslot.time.strftime('%Y-%m-%d'), announcement_text) self.assertIn( '(%s to %s UTC)' % ( timeslot.utc_start_time().strftime('%H:%M'),timeslot.utc_end_time().strftime('%H:%M') ), announcement_text) # Count number of sessions listed if base_session and extra_session: expected_session_matches = 3 elif base_session or extra_session: expected_session_matches = 2 else: expected_session_matches = 0 # no session list when only one session session_matches = re.findall(r'Session \d+:', announcement_text) self.assertEqual(len(session_matches), expected_session_matches) meeting_sessions = meeting.session_set.with_current_status() self.assertEqual(meeting_sessions.get(pk=session.pk).current_status, 'sched') if base_session: self.assertEqual(meeting_sessions.get(pk=base_session.pk).current_status, 'sched') if extra_session: self.assertEqual(meeting_sessions.get(pk=extra_session.pk).current_status, 'sched') if canceled_session: self.assertEqual(meeting_sessions.get(pk=canceled_session.pk).current_status, 'canceledpa') def test_interim_send_announcement(self): self.do_interim_send_announcement_test() def test_interim_send_announcement_with_base_sched(self): self.do_interim_send_announcement_test(base_session=True) def test_interim_send_announcement_with_extra_session(self): self.do_interim_send_announcement_test(extra_session=True) def test_interim_send_announcement_with_extra_session_and_base_sched(self): self.do_interim_send_announcement_test(extra_session=True, base_session=True) def test_interim_send_announcement_with_canceled_session(self): self.do_interim_send_announcement_test(canceled_session=True) def test_interim_send_announcement_with_canceled_session_and_base_sched(self): self.do_interim_send_announcement_test(canceled_session=True, base_session=True) def test_interim_send_announcement_with_extra_and_canceled_sessions(self): self.do_interim_send_announcement_test(extra_session=True, canceled_session=True) def test_interim_send_announcement_with_extra_and_canceled_sessions_and_base_sched(self): self.do_interim_send_announcement_test(extra_session=True, canceled_session=True, base_session=True) def do_interim_approve_by_ad_test(self, base_session=False, extra_session=False, canceled_session=False): make_interim_test_data() session = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='apprw').first() meeting = session.meeting if base_session: base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False) meeting.schedule.base = Schedule.objects.create( meeting=meeting, owner=PersonFactory(), name="base", visible=True, public=True ) SchedTimeSessAssignment.objects.create( timeslot=TimeSlotFactory.create(meeting=meeting), session=base_session, schedule=meeting.schedule.base, ) meeting.schedule.save() if extra_session: extra_session = SessionFactory(meeting=meeting, status_id='apprw') if canceled_session: canceled_session = SessionFactory(meeting=meeting, status_id='canceledpa') url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number}) length_before = len(outbox) login_testing_unauthorized(self, "ad", url) r = self.client.post(url, {'approve': 'approve'}) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_pending')) for sess in [session, base_session, extra_session]: if sess: self.assertEqual(Session.objects.with_current_status().get(pk=sess.pk).current_status, 'scheda') if canceled_session: self.assertEqual(Session.objects.with_current_status().get(pk=canceled_session.pk).current_status, 'canceledpa') self.assertEqual(len(outbox), length_before + 1) self.assertIn('ready for announcement', outbox[-1]['Subject']) def test_interim_approve_by_ad(self): self.do_interim_approve_by_ad_test() def test_interim_approve_by_ad_with_base_sched(self): self.do_interim_approve_by_ad_test(base_session=True) def test_interim_approve_by_ad_with_extra_session(self): self.do_interim_approve_by_ad_test(extra_session=True) def test_interim_approve_by_ad_with_extra_session_and_base_sched(self): self.do_interim_approve_by_ad_test(extra_session=True, base_session=True) def test_interim_approve_by_ad_with_canceled_session(self): self.do_interim_approve_by_ad_test(canceled_session=True) def test_interim_approve_by_ad_with_canceled_session_and_base_sched(self): self.do_interim_approve_by_ad_test(canceled_session=True, base_session=True) def test_interim_approve_by_ad_with_extra_and_canceled_sessions(self): self.do_interim_approve_by_ad_test(extra_session=True, canceled_session=True) def test_interim_approve_by_ad_with_extra_and_canceled_sessions_and_base_sched(self): self.do_interim_approve_by_ad_test(extra_session=True, canceled_session=True, base_session=True) def do_interim_approve_by_secretariat_test(self, base_session=False, extra_session=False, canceled_session=False): make_interim_test_data() session = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='apprw').first() meeting = session.meeting if base_session: base_session = SessionFactory(meeting=meeting, status_id='apprw', add_to_schedule=False) meeting.schedule.base = Schedule.objects.create( meeting=meeting, owner=PersonFactory(), name="base", visible=True, public=True ) SchedTimeSessAssignment.objects.create( timeslot=TimeSlotFactory.create(meeting=meeting), session=base_session, schedule=meeting.schedule.base, ) meeting.schedule.save() if extra_session: extra_session = SessionFactory(meeting=meeting, status_id='apprw') if canceled_session: canceled_session = SessionFactory(meeting=meeting, status_id='canceledpa') url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number}) length_before = len(outbox) login_testing_unauthorized(self, "secretary", url) r = self.client.post(url, {'approve': 'approve'}) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_send_announcement', kwargs={'number': meeting.number})) for sess in [session, base_session, extra_session]: if sess: self.assertEqual(Session.objects.with_current_status().get(pk=sess.pk).current_status, 'scheda') if canceled_session: self.assertEqual(Session.objects.with_current_status().get(pk=canceled_session.pk).current_status, 'canceledpa') self.assertEqual(len(outbox), length_before) def test_interim_approve_by_secretariat(self): self.do_interim_approve_by_secretariat_test() def test_interim_approve_by_secretariat_with_base_sched(self): self.do_interim_approve_by_secretariat_test(base_session=True) def test_interim_approve_by_secretariat_with_extra_session(self): self.do_interim_approve_by_secretariat_test(extra_session=True) def test_interim_approve_by_secretariat_with_extra_session_and_base_sched(self): self.do_interim_approve_by_secretariat_test(extra_session=True, base_session=True) def test_interim_approve_by_secretariat_with_canceled_session(self): self.do_interim_approve_by_secretariat_test(canceled_session=True) def test_interim_approve_by_secretariat_with_canceled_session_and_base_sched(self): self.do_interim_approve_by_secretariat_test(canceled_session=True, base_session=True) def test_interim_approve_by_secretariat_with_extra_and_canceled_sessions(self): self.do_interim_approve_by_secretariat_test(extra_session=True, canceled_session=True) def test_interim_approve_by_secretariat_with_extra_and_canceled_sessions_and_base_sched(self): self.do_interim_approve_by_secretariat_test(extra_session=True, canceled_session=True, base_session=True) def test_past(self): today = datetime.date.today() last_week = today - datetime.timedelta(days=7) ietf = SessionFactory(meeting__type_id='ietf',meeting__date=last_week,group__state_id='active',group__parent=GroupFactory(state_id='active')) SessionFactory(meeting__type_id='interim',meeting__date=last_week,status_id='canceled',group__state_id='active',group__parent=GroupFactory(state_id='active')) url = urlreverse('ietf.meeting.views.past') r = self.client.get(url) self.assertContains(r, 'IETF - %02d'%int(ietf.meeting.number)) q = PyQuery(r.content) #id="-%s" % interim.group.acronym #self.assertIn('CANCELLED', q('[id*="'+id+'"]').text()) self.assertIn('CANCELLED', q('tr>td>a>span').text()) def do_upcoming_test(self, querystring=None, create_meeting=True): if create_meeting: make_meeting_test_data(create_interims=True) url = urlreverse("ietf.meeting.views.upcoming") if querystring is not None: url += '?' + querystring today = datetime.date.today() interims = dict( mars=add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='mars')).filter(current_status='sched').first().meeting, ames=add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', meeting__date__gt=today, group__acronym='ames')).filter(current_status='canceled').first().meeting, ) return self.client.get(url), interims def test_upcoming(self): r, interims = self.do_upcoming_test() self.assertContains(r, interims['mars'].number) self.assertContains(r, interims['ames'].number) self.assertContains(r, 'IETF 72') # cancelled session q = PyQuery(r.content) self.assertIn('CANCELLED', q('tr>td.text-right>span').text()) # test_upcoming_filters_ignored removed - we _don't_ want to ignore filters now, and the test passed because it wasn't testing the filtering anyhow (which requires testing the js). def test_upcoming_ical(self): meeting = make_meeting_test_data(create_interims=True) populate_important_dates(meeting) url = urlreverse("ietf.meeting.views.upcoming_ical") r = self.client.get(url) self.assertEqual(r.status_code, 200) # Expect events 3 sessions - one for each WG and one for the IETF meeting assert_ical_response_is_valid(self, r, expected_event_summaries=[ 'ames - Asteroid Mining Equipment Standardization Group', 'mars - Martian Special Interest Group', 'IETF 72', ], expected_event_count=3) def test_upcoming_ical_filter(self): # Just a quick check of functionality - details tested by test_js.InterimTests make_meeting_test_data(create_interims=True) url = urlreverse("ietf.meeting.views.upcoming_ical") r = self.client.get(url + '?show=mars') self.assertEqual(r.status_code, 200) assert_ical_response_is_valid(self, r, expected_event_summaries=[ 'mars - Martian Special Interest Group', 'IETF 72', ], expected_event_count=2) def test_upcoming_json(self): make_meeting_test_data(create_interims=True) url = urlreverse("ietf.meeting.views.upcoming_json") r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertEqual(r.get('Content-Type'), "application/json;charset=utf-8") data = r.json() self.assertEqual(len(data), 3) def test_interim_request_permissions(self): '''Ensure only authorized users see link to request interim meeting''' make_meeting_test_data() # test unauthorized not logged in upcoming_url = urlreverse("ietf.meeting.views.upcoming") request_url = urlreverse("ietf.meeting.views.interim_request") r = self.client.get(upcoming_url) self.assertNotContains(r,'Request new interim meeting') # test unauthorized user login_testing_unauthorized(self,"plain",request_url) r = self.client.get(upcoming_url) self.assertNotContains(r,'Request new interim meeting') r = self.client.get(request_url) self.assertEqual(r.status_code, 403) self.client.logout() # test authorized for username in ('secretary','ad','marschairman','irtf-chair','irgchairman'): self.client.login(username=username, password= username + "+password") r = self.client.get(upcoming_url) self.assertContains(r,'Request new interim meeting') r = self.client.get(request_url) self.assertEqual(r.status_code, 200) self.client.logout() def test_interim_request_options(self): make_meeting_test_data() # secretariat can request for any group self.client.login(username="secretary", password="secretary+password") r = self.client.get("/meeting/interim/request/") self.assertEqual(r.status_code, 200) q = PyQuery(r.content) Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof')) self.assertEqual(Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof')).count(), len(q("#id_group option")) - 1) # -1 for options placeholder self.client.logout() # wg chair self.client.login(username="marschairman", password="marschairman+password") r = self.client.get("/meeting/interim/request/") self.assertEqual(r.status_code, 200) q = PyQuery(r.content) user = User.objects.get(username='marschairman') person = user.person count = person.role_set.filter(name='chair',group__type__in=('wg', 'rg'), group__state__in=('active', 'proposed')).count() self.assertEqual(count, len(q("#id_group option")) - 1) # -1 for options placeholder # wg AND rg chair group = Group.objects.get(acronym='irg') Role.objects.create(name_id='chair',group=group,person=person,email=person.email()) r = self.client.get("/meeting/interim/request/") self.assertEqual(r.status_code, 200) q = PyQuery(r.content) count = person.role_set.filter(name='chair',group__type__in=('wg', 'rg'), group__state__in=('active', 'proposed')).count() self.assertEqual(count, len(q("#id_group option")) - 1) # -1 for options placeholder def do_interim_request_single_virtual(self, emails_expected): make_meeting_test_data() group = Group.objects.get(acronym='mars') date = datetime.date.today() + datetime.timedelta(days=30) time = datetime.datetime.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) duration = datetime.timedelta(hours=3) remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' length_before = len(outbox) meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count() next_num = "%02d" % (meeting_count+1) self.client.login(username="marschairman", password="marschairman+password") data = {'group':group.pk, 'meeting_type':'single', 'city':'', 'country':'', 'time_zone':'UTC', 'session_set-0-date':date.strftime("%Y-%m-%d"), 'session_set-0-time':time.strftime('%H:%M'), 'session_set-0-requested_duration':'03:00:00', 'session_set-0-remote_instructions':remote_instructions, 'session_set-0-agenda':agenda, 'session_set-0-agenda_note':agenda_note, 'session_set-TOTAL_FORMS':1, 'session_set-INITIAL_FORMS':0, 'session_set-MIN_NUM_FORMS':0, 'session_set-MAX_NUM_FORMS':1000} r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming')) meeting = Meeting.objects.order_by('id').last() self.assertEqual(meeting.type_id,'interim') self.assertEqual(meeting.date,date) self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num)) self.assertEqual(meeting.city,'') self.assertEqual(meeting.country,'') self.assertEqual(meeting.time_zone,'UTC') session = meeting.session_set.first() self.assertEqual(session.remote_instructions,remote_instructions) self.assertEqual(session.agenda_note,agenda_note) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt) self.assertEqual(timeslot.duration,duration) # ensure agenda document was created self.assertEqual(session.materials.count(),1) doc = session.materials.first() path = os.path.join(doc.get_file_path(),doc.filename_with_rev()) self.assertTrue(os.path.exists(path)) # check notices to secretariat and chairs self.assertEqual(len(outbox), length_before + emails_expected) return meeting @override_settings(VIRTUAL_INTERIMS_REQUIRE_APPROVAL = True) def test_interim_request_single_virtual_settings_approval_required(self): meeting = self.do_interim_request_single_virtual(emails_expected=1) self.assertEqual(meeting.session_set.last().schedulingevent_set.last().status_id,'apprw') self.assertIn('New Interim Meeting Request', outbox[-1]['Subject']) self.assertIn('session-request@ietf.org', outbox[-1]['To']) self.assertIn('aread@example.org', outbox[-1]['Cc']) @override_settings(VIRTUAL_INTERIMS_REQUIRE_APPROVAL = False) def test_interim_request_single_virtual_settings_approval_not_required(self): meeting = self.do_interim_request_single_virtual(emails_expected=2) self.assertEqual(meeting.session_set.last().schedulingevent_set.last().status_id,'scheda') self.assertIn('iesg-secretary@ietf.org', outbox[-1]['To']) self.assertIn('interim meeting ready for announcement', outbox[-1]['Subject']) def test_interim_request_single_in_person(self): make_meeting_test_data() group = Group.objects.get(acronym='mars') date = datetime.date.today() + datetime.timedelta(days=30) time = datetime.datetime.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) duration = datetime.timedelta(hours=3) city = 'San Francisco' country = 'US' time_zone = 'America/Los_Angeles' remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count() next_num = "%02d" % (meeting_count+1) self.client.login(username="secretary", password="secretary+password") data = {'group':group.pk, 'meeting_type':'single', 'city':city, 'country':country, 'time_zone':time_zone, 'session_set-0-date':date.strftime("%Y-%m-%d"), 'session_set-0-time':time.strftime('%H:%M'), 'session_set-0-requested_duration':'03:00:00', 'session_set-0-remote_instructions':remote_instructions, 'session_set-0-agenda':agenda, 'session_set-0-agenda_note':agenda_note, 'session_set-TOTAL_FORMS':1, 'session_set-INITIAL_FORMS':0} r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming')) meeting = Meeting.objects.order_by('id').last() self.assertEqual(meeting.type_id,'interim') self.assertEqual(meeting.date,date) self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num)) self.assertEqual(meeting.city,city) self.assertEqual(meeting.country,country) self.assertEqual(meeting.time_zone,time_zone) session = meeting.session_set.first() self.assertEqual(session.remote_instructions,remote_instructions) self.assertEqual(session.agenda_note,agenda_note) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt) self.assertEqual(timeslot.duration,duration) def test_interim_request_multi_day(self): make_meeting_test_data() date = datetime.date.today() + datetime.timedelta(days=30) date2 = date + datetime.timedelta(days=1) time = datetime.datetime.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) dt2 = datetime.datetime.combine(date2, time) duration = datetime.timedelta(hours=3) group = Group.objects.get(acronym='mars') city = 'San Francisco' country = 'US' time_zone = 'America/Los_Angeles' remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count() next_num = "%02d" % (meeting_count+1) self.client.login(username="secretary", password="secretary+password") data = {'group':group.pk, 'meeting_type':'multi-day', 'city':city, 'country':country, 'time_zone':time_zone, 'session_set-0-date':date.strftime("%Y-%m-%d"), 'session_set-0-time':time.strftime('%H:%M'), 'session_set-0-requested_duration':'03:00:00', 'session_set-0-remote_instructions':remote_instructions, 'session_set-0-agenda':agenda, 'session_set-0-agenda_note':agenda_note, 'session_set-1-date':date2.strftime("%Y-%m-%d"), 'session_set-1-time':time.strftime('%H:%M'), 'session_set-1-requested_duration':'03:00:00', 'session_set-1-remote_instructions':remote_instructions, 'session_set-1-agenda':agenda, 'session_set-1-agenda_note':agenda_note, 'session_set-TOTAL_FORMS':2, 'session_set-INITIAL_FORMS':0} r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming')) meeting = Meeting.objects.order_by('id').last() self.assertEqual(meeting.type_id,'interim') self.assertEqual(meeting.date,date) self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num)) self.assertEqual(meeting.city,city) self.assertEqual(meeting.country,country) self.assertEqual(meeting.time_zone,time_zone) self.assertEqual(meeting.session_set.count(),2) # first sesstion session = meeting.session_set.all()[0] self.assertEqual(session.remote_instructions,remote_instructions) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt) self.assertEqual(timeslot.duration,duration) self.assertEqual(session.agenda_note,agenda_note) # second sesstion session = meeting.session_set.all()[1] self.assertEqual(session.remote_instructions,remote_instructions) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt2) self.assertEqual(timeslot.duration,duration) self.assertEqual(session.agenda_note,agenda_note) def test_interim_request_multi_day_non_consecutive(self): make_meeting_test_data() date = datetime.date.today() + datetime.timedelta(days=30) date2 = date + datetime.timedelta(days=2) time = datetime.datetime.now().time().replace(microsecond=0,second=0) group = Group.objects.get(acronym='mars') city = 'San Francisco' country = 'US' time_zone = 'America/Los_Angeles' remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' self.client.login(username="secretary", password="secretary+password") data = {'group':group.pk, 'meeting_type':'multi-day', 'city':city, 'country':country, 'time_zone':time_zone, 'session_set-0-date':date.strftime("%Y-%m-%d"), 'session_set-0-time':time.strftime('%H:%M'), 'session_set-0-requested_duration':'03:00:00', 'session_set-0-remote_instructions':remote_instructions, 'session_set-0-agenda':agenda, 'session_set-0-agenda_note':agenda_note, 'session_set-1-date':date2.strftime("%Y-%m-%d"), 'session_set-1-time':time.strftime('%H:%M'), 'session_set-1-requested_duration':'03:00:00', 'session_set-1-remote_instructions':remote_instructions, 'session_set-1-agenda':agenda, 'session_set-1-agenda_note':agenda_note, 'session_set-TOTAL_FORMS':2, 'session_set-INITIAL_FORMS':0} r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) self.assertContains(r, 'days must be consecutive') def test_interim_request_multi_day_cancel(self): """All sessions of a multi-day interim request should be canceled""" length_before = len(outbox) date = datetime.date.today()+datetime.timedelta(days=15) # Set up an interim request with several sessions num_sessions = 3 meeting = MeetingFactory(type_id='interim', date=date) for _ in range(num_sessions): SessionFactory(meeting=meeting) # Cancel the interim request url = urlreverse('ietf.meeting.views.interim_request_cancel', kwargs={'number': meeting.number}) self.client.login(username="secretary", password="secretary+password") r = self.client.post(url) # Verify results self.assertRedirects(r, urlreverse('ietf.meeting.views.upcoming')) for session in add_event_info_to_session_qs(meeting.session_set.all()): self.assertEqual(session.current_status, 'canceled') self.assertEqual(len(outbox), length_before + 1) self.assertIn('Interim Meeting Cancelled', outbox[-1]['Subject']) def test_interim_request_series(self): make_meeting_test_data() meeting_count_before = Meeting.objects.filter(type='interim').count() date = datetime.date.today() + datetime.timedelta(days=30) if (date.month, date.day) == (12, 31): # Avoid date and date2 in separate years # (otherwise the test will fail if run on December 1st) date += datetime.timedelta(days=1) date2 = date + datetime.timedelta(days=1) # ensure dates are in the same year if date.year != date2.year: date += datetime.timedelta(days=1) date2 += datetime.timedelta(days=1) time = datetime.datetime.now().time().replace(microsecond=0,second=0) dt = datetime.datetime.combine(date, time) dt2 = datetime.datetime.combine(date2, time) duration = datetime.timedelta(hours=3) group = Group.objects.get(acronym='mars') city = '' country = '' time_zone = 'America/Los_Angeles' remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count() next_num = "%02d" % (meeting_count+1) next_num2 = "%02d" % (meeting_count+2) self.client.login(username="secretary", password="secretary+password") r = self.client.get(urlreverse("ietf.meeting.views.interim_request")) self.assertEqual(r.status_code, 200) data = {'group':group.pk, 'meeting_type':'series', 'city':city, 'country':country, 'time_zone':time_zone, 'session_set-0-date':date.strftime("%Y-%m-%d"), 'session_set-0-time':time.strftime('%H:%M'), 'session_set-0-requested_duration':'03:00:00', 'session_set-0-remote_instructions':remote_instructions, 'session_set-0-agenda':agenda, 'session_set-0-agenda_note':agenda_note, 'session_set-1-date':date2.strftime("%Y-%m-%d"), 'session_set-1-time':time.strftime('%H:%M'), 'session_set-1-requested_duration':'03:00:00', 'session_set-1-remote_instructions':remote_instructions, 'session_set-1-agenda':agenda, 'session_set-1-agenda_note':agenda_note, 'session_set-TOTAL_FORMS':2, 'session_set-INITIAL_FORMS':0} r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming')) meeting_count_after = Meeting.objects.filter(type='interim').count() self.assertEqual(meeting_count_after,meeting_count_before + 2) meetings = Meeting.objects.order_by('-id')[:2] # first meeting meeting = meetings[1] self.assertEqual(meeting.type_id,'interim') self.assertEqual(meeting.date,date) self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num)) self.assertEqual(meeting.city,city) self.assertEqual(meeting.country,country) self.assertEqual(meeting.time_zone,time_zone) self.assertEqual(meeting.session_set.count(),1) session = meeting.session_set.first() self.assertEqual(session.remote_instructions,remote_instructions) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt) self.assertEqual(timeslot.duration,duration) self.assertEqual(session.agenda_note,agenda_note) # second meeting meeting = meetings[0] self.assertEqual(meeting.type_id,'interim') self.assertEqual(meeting.date,date2) self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date2.year, group.acronym, next_num2)) self.assertEqual(meeting.city,city) self.assertEqual(meeting.country,country) self.assertEqual(meeting.time_zone,time_zone) self.assertEqual(meeting.session_set.count(),1) session = meeting.session_set.first() self.assertEqual(session.remote_instructions,remote_instructions) timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,dt2) self.assertEqual(timeslot.duration,duration) self.assertEqual(session.agenda_note,agenda_note) # test_interim_pending subsumed by test_appears_on_pending def test_can_approve_interim_request(self): make_interim_test_data() # unprivileged user user = User.objects.get(username='plain') group = Group.objects.get(acronym='mars') meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group=group)).filter(current_status='apprw').first().meeting self.assertFalse(can_approve_interim_request(meeting=meeting,user=user)) # Secretariat user = User.objects.get(username='secretary') self.assertTrue(can_approve_interim_request(meeting=meeting,user=user)) # related AD user = User.objects.get(username='ad') self.assertTrue(can_approve_interim_request(meeting=meeting,user=user)) # AD from other area user = User.objects.get(username='ops-ad') self.assertFalse(can_approve_interim_request(meeting=meeting,user=user)) # AD from other area assigned as the WG AD anyhow (cross-area AD) user = RoleFactory(name_id='ad',group=group).person.user self.assertTrue(can_approve_interim_request(meeting=meeting,user=user)) # WG Chair user = User.objects.get(username='marschairman') self.assertFalse(can_approve_interim_request(meeting=meeting,user=user)) def test_can_view_interim_request(self): make_interim_test_data() # unprivileged user user = User.objects.get(username='plain') group = Group.objects.get(acronym='mars') meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group=group)).filter(current_status='apprw').first().meeting self.assertFalse(can_view_interim_request(meeting=meeting,user=user)) # Secretariat user = User.objects.get(username='secretary') self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) # related AD user = User.objects.get(username='ad') self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) # other AD user = User.objects.get(username='ops-ad') self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) # WG Chair user = User.objects.get(username='marschairman') self.assertTrue(can_view_interim_request(meeting=meeting,user=user)) # Other WG Chair user = User.objects.get(username='ameschairman') self.assertFalse(can_view_interim_request(meeting=meeting,user=user)) def test_can_manage_group(self): make_meeting_test_data() # unprivileged user user = User.objects.get(username='plain') group = Group.objects.get(acronym='mars') self.assertFalse(can_manage_group(user=user,group=group)) # Secretariat user = User.objects.get(username='secretary') self.assertTrue(can_manage_group(user=user,group=group)) # related AD user = User.objects.get(username='ad') self.assertTrue(can_manage_group(user=user,group=group)) # other AD user = User.objects.get(username='ops-ad') self.assertTrue(can_manage_group(user=user,group=group)) # WG Chair user = User.objects.get(username='marschairman') self.assertTrue(can_manage_group(user=user,group=group)) # Other WG Chair user = User.objects.get(username='ameschairman') self.assertFalse(can_manage_group(user=user,group=group)) def test_interim_request_details(self): make_interim_test_data() meeting = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='apprw').first().meeting url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) start_time = meeting.session_set.first().official_timeslotassignment().timeslot.time.strftime('%H:%M') utc_start_time = meeting.session_set.first().official_timeslotassignment().timeslot.utc_start_time().strftime('%H:%M') self.assertIn(start_time, unicontent(r)) self.assertIn(utc_start_time, unicontent(r)) def test_interim_request_details_announcement(self): '''Test access to Announce / Skip Announce features''' make_meeting_test_data() date = datetime.date.today() + datetime.timedelta(days=30) group = Group.objects.get(acronym='mars') meeting = make_interim_meeting(group=group, date=date, status='scheda') url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) # Chair, no access self.client.login(username="marschairman", password="marschairman+password") r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q("a.btn:contains('Announce')")),0) # Secretariat has access self.client.login(username="secretary", password="secretary+password") r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q("a.btn:contains('Announce')")),2) def test_interim_request_details_cancel(self): """Test access to cancel meeting / session features""" make_interim_test_data() mars_sessions = Session.objects.with_current_status( ).filter( meeting__type='interim', group__acronym='mars', ) meeting_apprw = mars_sessions.filter(current_status='apprw').first().meeting meeting_sched = mars_sessions.filter(current_status='sched').first().meeting # All these roles should have access to cancel the request usernames_and_passwords = ( ('marschairman', 'marschairman+password'), ('secretary', 'secretary+password') ) # Start with one session - there should not be any cancel session buttons for meeting in (meeting_apprw, meeting_sched): url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number}) for username, password in usernames_and_passwords: self.client.login(username=username, password=password) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) cancel_meeting_btns = q("a.btn:contains('Cancel Meeting')") self.assertEqual(len(cancel_meeting_btns), 1, 'Should be exactly one cancel meeting button for user %s' % username) self.assertEqual(cancel_meeting_btns.eq(0).attr('href'), urlreverse('ietf.meeting.views.interim_request_cancel', kwargs={'number': meeting.number}), 'Cancel meeting points to wrong URL') self.assertEqual(len(q("a.btn:contains('Cancel Session')")), 0, 'Should be no cancel session buttons for user %s' % username) # Add a second session SessionFactory(meeting=meeting_apprw, status_id='apprw') SessionFactory(meeting=meeting_sched, status_id='sched') for meeting in (meeting_apprw, meeting_sched): url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number}) for username, password in usernames_and_passwords: self.client.login(username=username, password=password) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) cancel_meeting_btns = q("a.btn:contains('Cancel Meeting')") self.assertEqual(len(cancel_meeting_btns), 1, 'Should be exactly one cancel meeting button for user %s' % username) self.assertEqual(cancel_meeting_btns.eq(0).attr('href'), urlreverse('ietf.meeting.views.interim_request_cancel', kwargs={'number': meeting.number}), 'Cancel meeting button points to wrong URL') cancel_session_btns = q("a.btn:contains('Cancel Session')") self.assertEqual(len(cancel_session_btns), 2, 'Should be two cancel session buttons for user %s' % username) hrefs = [btn.attr('href') for btn in cancel_session_btns.items()] for index, session in enumerate(meeting.session_set.all()): self.assertIn(urlreverse('ietf.meeting.views.interim_request_session_cancel', kwargs={'sessionid': session.pk}), hrefs, 'Session missing a link to its cancel URL') def test_interim_request_details_status(self): """Test statuses on the interim request details page""" make_interim_test_data() some_person = PersonFactory() self.client.login(username='marschairman', password='marschairman+password') # These are the first sessions for each meeting - hang on to them sessions = list( Session.objects.with_current_status().filter(meeting__type='interim', group__acronym='mars') ) # Hack: change the name for the 'canceled' session status so we can tell it apart # from the 'canceledpa' session status more easily canceled_status = SessionStatusName.objects.get(slug='canceled') canceled_status.name = 'This is cancelled' canceled_status.save() canceledpa_status = SessionStatusName.objects.get(slug='canceledpa') notmeet_status = SessionStatusName.objects.get(slug='notmeet') # Simplest case - single session for each meeting for session in [Session.objects.with_current_status().get(pk=s.pk) for s in sessions]: url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': session.meeting.number}) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) status = SessionStatusName.objects.get(slug=session.current_status) self.assertEqual( len(q("dd:contains('%s')" % status.name)), 1 # once - for the meeting status, no session status shown when only one session ) # Now add a second session with a different status - it should not change meeting status for session in [Session.objects.with_current_status().get(pk=s.pk) for s in sessions]: SessionFactory(meeting=session.meeting, status_id=notmeet_status.pk) url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': session.meeting.number}) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) status = SessionStatusName.objects.get(slug=session.current_status) self.assertEqual( len(q("dd:contains('%s')" % status.name)), 2 # twice - once as the meeting status, once as the session status ) self.assertEqual( len(q("dd:contains('%s')" % notmeet_status.name)), 1 # only for the session status ) # Now cancel the first session - second meeting status should be shown for meeting for session in [Session.objects.with_current_status().get(pk=s.pk) for s in sessions]: # Use 'canceledpa' here and 'canceled' later SchedulingEvent.objects.create(session=session, status=canceledpa_status, by=some_person) url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': session.meeting.number}) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual( len(q("dd:contains('%s')" % canceledpa_status.name)), 1 # only for the session status ) self.assertEqual( len(q("dd:contains('%s')" % notmeet_status.name)), 2 # twice - once as the meeting status, once as the session status ) # Now cancel the second session - first meeting status should be shown for meeting again for session in [Session.objects.with_current_status().get(pk=s.pk) for s in sessions]: second_session = session.meeting.session_set.exclude(pk=session.pk).first() # use canceled so we can differentiate between the first and second session statuses SchedulingEvent.objects.create(session=second_session, status=canceled_status, by=some_person) url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': session.meeting.number}) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual( len(q("dd:contains('%s')" % canceledpa_status.name)), 2 # twice - once as the meeting status, once as the session status ) self.assertEqual( len(q("dd:contains('%s')" % canceled_status.name)), 1 # only as the session status ) def do_interim_request_disapprove_test(self, extra_session=False, canceled_session=False): make_interim_test_data() session = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='apprw').first() meeting = session.meeting if extra_session: extra_session = SessionFactory(meeting=meeting, status_id='apprw') if canceled_session: canceled_session = SessionFactory(meeting=meeting, status_id='canceledpa') url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) login_testing_unauthorized(self,"secretary",url) r = self.client.post(url,{'disapprove':'Disapprove'}) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_pending')) for sess in [session, extra_session]: if sess: self.assertEqual(Session.objects.with_current_status().get(pk=sess.pk).current_status, 'disappr') if canceled_session: self.assertEqual(Session.objects.with_current_status().get(pk=canceled_session.pk).current_status, 'canceledpa') def test_interim_request_disapprove(self): self.do_interim_request_disapprove_test() def test_interim_request_disapprove_with_extra_session(self): self.do_interim_request_disapprove_test(extra_session=True) def test_interim_request_disapprove_with_canceled_session(self): self.do_interim_request_disapprove_test(canceled_session=True) def test_interim_request_disapprove_with_extra_and_canceled_sessions(self): self.do_interim_request_disapprove_test(extra_session=True, canceled_session=True) def test_interim_request_cancel(self): """Test that interim request cancel function works Does not test that UI buttons are present, that is handled elsewhere. """ make_interim_test_data() meeting = Session.objects.with_current_status( ).filter( meeting__type='interim', group__acronym='mars', current_status='apprw', ).first().meeting # ensure fail unauthorized url = urlreverse('ietf.meeting.views.interim_request_cancel', kwargs={'number': meeting.number}) comments = 'Bob cannot make it' self.client.login(username="ameschairman", password="ameschairman+password") r = self.client.post(url, {'comments': comments}) self.assertEqual(r.status_code, 403) # test cancelling before announcement self.client.login(username="marschairman", password="marschairman+password") length_before = len(outbox) r = self.client.post(url, {'comments': comments}) self.assertRedirects(r, urlreverse('ietf.meeting.views.upcoming')) for session in meeting.session_set.with_current_status(): self.assertEqual(session.current_status,'canceledpa') self.assertEqual(session.agenda_note, comments) self.assertEqual(len(outbox), length_before) # no email notice # test cancelling after announcement meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='sched').first().meeting url = urlreverse('ietf.meeting.views.interim_request_cancel', kwargs={'number': meeting.number}) r = self.client.post(url, {'comments': comments}) self.assertRedirects(r, urlreverse('ietf.meeting.views.upcoming')) for session in meeting.session_set.with_current_status(): self.assertEqual(session.current_status,'canceled') self.assertEqual(session.agenda_note, comments) self.assertEqual(len(outbox), length_before + 1) self.assertIn('Interim Meeting Cancelled', outbox[-1]['Subject']) def test_interim_request_session_cancel(self): """Test that interim meeting session cancellation functions Does not test that UI buttons are present, that is handled elsewhere. """ make_interim_test_data() session = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='apprw',).first() meeting = session.meeting comments = 'Bob cannot make it' # Should not be able to cancel when there is only one session self.client.login(username="marschairman", password="marschairman+password") url = urlreverse('ietf.meeting.views.interim_request_session_cancel', kwargs={'sessionid': session.pk}) r = self.client.post(url, {'comments': comments}) self.assertEqual(r.status_code, 409) # Add a second session SessionFactory(meeting=meeting, status_id='apprw') # ensure fail unauthorized url = urlreverse('ietf.meeting.views.interim_request_session_cancel', kwargs={'sessionid': session.pk}) self.client.login(username="ameschairman", password="ameschairman+password") r = self.client.post(url, {'comments': comments}) self.assertEqual(r.status_code, 403) # test cancelling before announcement self.client.login(username="marschairman", password="marschairman+password") length_before = len(outbox) canceled_count_before = meeting.session_set.with_current_status().filter( current_status__in=['canceled', 'canceledpa']).count() r = self.client.post(url, {'comments': comments}) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})) # This session should be canceled... sessions = meeting.session_set.with_current_status() session = sessions.filter(id=session.pk).first() # reload our session info self.assertEqual(session.current_status, 'canceledpa') self.assertEqual(session.agenda_note, comments) # But others should not - count should have changed by only 1 self.assertEqual( sessions.filter(current_status__in=['canceled', 'canceledpa']).count(), canceled_count_before + 1 ) self.assertEqual(len(outbox), length_before) # no email notice # test cancelling after announcement session = Session.objects.with_current_status().filter( meeting__type='interim', group__acronym='mars', current_status='sched').first() meeting = session.meeting # Try to cancel when there's only one session in the meeting url = urlreverse('ietf.meeting.views.interim_request_session_cancel', kwargs={'sessionid': session.pk}) r = self.client.post(url, {'comments': comments}) self.assertEqual(r.status_code, 409) # Add another session SessionFactory(meeting=meeting, status_id='sched') # two sessions so canceling a session makes sense canceled_count_before = meeting.session_set.with_current_status().filter( current_status__in=['canceled', 'canceledpa']).count() r = self.client.post(url, {'comments': comments}) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})) # This session should be canceled... sessions = meeting.session_set.with_current_status() session = sessions.filter(id=session.pk).first() # reload our session info self.assertEqual(session.current_status, 'canceled') self.assertEqual(session.agenda_note, comments) # But others should not - count should have changed by only 1 self.assertEqual( sessions.filter(current_status__in=['canceled', 'canceledpa']).count(), canceled_count_before + 1 ) self.assertEqual(len(outbox), length_before + 1) # email notice sent self.assertIn('session cancelled', outbox[-1]['Subject']) def test_interim_request_edit_no_notice(self): '''Edit a request. No notice should go out if it hasn't been announced yet''' make_interim_test_data() meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting group = meeting.session_set.first().group url = urlreverse('ietf.meeting.views.interim_request_edit', kwargs={'number': meeting.number}) # test unauthorized access self.client.login(username="ameschairman", password="ameschairman+password") r = self.client.get(url) self.assertEqual(r.status_code, 403) # test authorized use login_testing_unauthorized(self, "secretary", url) r = self.client.get(url) self.assertEqual(r.status_code, 200) # post changes length_before = len(outbox) form_initial = r.context['form'].initial formset_initial = r.context['formset'].forms[0].initial new_time = formset_initial['time'] + datetime.timedelta(hours=1) data = {'group':group.pk, 'meeting_type':'single', 'session_set-0-id':meeting.session_set.first().id, 'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'), 'session_set-0-time':new_time.strftime('%H:%M'), 'session_set-0-requested_duration': '00:30', 'session_set-0-remote_instructions':formset_initial['remote_instructions'], #'session_set-0-agenda':formset_initial['agenda'], 'session_set-0-agenda_note':formset_initial['agenda_note'], 'session_set-TOTAL_FORMS':1, 'session_set-INITIAL_FORMS':1} data.update(form_initial) r = self.client.post(url, data) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})) self.assertEqual(len(outbox),length_before) session = meeting.session_set.first() timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,new_time) def test_interim_request_edit(self): '''Edit request. Send notice of change''' make_interim_test_data() meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='sched').first().meeting group = meeting.session_set.first().group url = urlreverse('ietf.meeting.views.interim_request_edit', kwargs={'number': meeting.number}) # test unauthorized access self.client.login(username="ameschairman", password="ameschairman+password") r = self.client.get(url) self.assertEqual(r.status_code, 403) # test authorized use login_testing_unauthorized(self, "secretary", url) r = self.client.get(url) self.assertEqual(r.status_code, 200) # post changes length_before = len(outbox) form_initial = r.context['form'].initial formset_initial = r.context['formset'].forms[0].initial new_time = formset_initial['time'] + datetime.timedelta(hours=1) new_duration = formset_initial['requested_duration'] + datetime.timedelta(hours=1) data = {'group':group.pk, 'meeting_type':'single', 'session_set-0-id':meeting.session_set.first().id, 'session_set-0-date':formset_initial['date'].strftime('%Y-%m-%d'), 'session_set-0-time':new_time.strftime('%H:%M'), 'session_set-0-requested_duration':self.strfdelta(new_duration, '{hours}:{minutes}'), 'session_set-0-remote_instructions':formset_initial['remote_instructions'], #'session_set-0-agenda':formset_initial['agenda'], 'session_set-0-agenda_note':formset_initial['agenda_note'], 'session_set-TOTAL_FORMS':1, 'session_set-INITIAL_FORMS':1} data.update(form_initial) r = self.client.post(url, data) self.assertRedirects(r, urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})) self.assertEqual(len(outbox),length_before+1) self.assertIn('CHANGED', outbox[-1]['Subject']) session = meeting.session_set.first() timeslot = session.official_timeslotassignment().timeslot self.assertEqual(timeslot.time,new_time) self.assertEqual(timeslot.duration,new_duration) def strfdelta(self, tdelta, fmt): d = {"days": tdelta.days} d["hours"], rem = divmod(tdelta.seconds, 3600) d["minutes"], d["seconds"] = divmod(rem, 60) return fmt.format(**d) def test_interim_request_details_permissions(self): make_interim_test_data() meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting url = urlreverse('ietf.meeting.views.interim_request_details',kwargs={'number':meeting.number}) # unprivileged user login_testing_unauthorized(self,"plain",url) r = self.client.get(url) self.assertEqual(r.status_code, 403) def test_send_interim_approval_request(self): make_interim_test_data() meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting length_before = len(outbox) send_interim_approval_request(meetings=[meeting]) self.assertEqual(len(outbox),length_before+1) self.assertIn('New Interim Meeting Request', outbox[-1]['Subject']) def test_send_interim_meeting_cancellation_notice(self): make_interim_test_data() meeting = Session.objects.with_current_status( ).filter( meeting__type='interim', group__acronym='mars', current_status='sched', ).first().meeting length_before = len(outbox) send_interim_meeting_cancellation_notice(meeting) self.assertEqual(len(outbox),length_before + 1) self.assertIn('Interim Meeting Cancelled', outbox[-1]['Subject']) def test_send_interim_session_cancellation_notice(self): make_interim_test_data() session = Session.objects.with_current_status( ).filter( meeting__type='interim', group__acronym='mars', current_status='sched', ).first() length_before = len(outbox) send_interim_session_cancellation_notice(session) self.assertEqual(len(outbox), length_before + 1) self.assertIn('session cancelled', outbox[-1]['Subject']) def test_send_interim_minutes_reminder(self): make_meeting_test_data() group = Group.objects.get(acronym='mars') date = datetime.datetime.today() - datetime.timedelta(days=10) meeting = make_interim_meeting(group=group, date=date, status='sched') length_before = len(outbox) send_interim_minutes_reminder(meeting=meeting) self.assertEqual(len(outbox),length_before+1) self.assertIn('Action Required: Minutes', outbox[-1]['Subject']) def test_group_ical(self): make_interim_test_data() meeting = Meeting.objects.filter(type='interim', session__group__acronym='mars').first() s1 = Session.objects.filter(meeting=meeting, group__acronym="mars").first() a1 = s1.official_timeslotassignment() t1 = a1.timeslot # Create an extra session t2 = TimeSlotFactory.create(meeting=meeting, time=datetime.datetime.combine(meeting.date, datetime.time(11, 30))) s2 = SessionFactory.create(meeting=meeting, group=s1.group, add_to_schedule=False) SchedTimeSessAssignment.objects.create(timeslot=t2, session=s2, schedule=meeting.schedule) # url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'acronym':s1.group.acronym, }) r = self.client.get(url) self.assertEqual(r.get('Content-Type'), "text/calendar") self.assertContains(r, 'BEGIN:VEVENT') self.assertEqual(r.content.count(b'UID'), 2) self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group') self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S')) self.assertContains(r, t2.time.strftime('%Y%m%dT%H%M%S')) self.assertContains(r, 'END:VEVENT') # url = urlreverse('ietf.meeting.views.agenda_ical', kwargs={'num':meeting.number, 'session_id':s1.id, }) r = self.client.get(url) self.assertEqual(r.get('Content-Type'), "text/calendar") self.assertContains(r, 'BEGIN:VEVENT') self.assertEqual(r.content.count(b'UID'), 1) self.assertContains(r, 'SUMMARY:mars - Martian Special Interest Group') self.assertContains(r, t1.time.strftime('%Y%m%dT%H%M%S')) self.assertNotContains(r, t2.time.strftime('%Y%m%dT%H%M%S')) self.assertContains(r, 'END:VEVENT') class AjaxTests(TestCase): def test_ajax_get_utc(self): # test bad queries url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=badtime&timezone=UTC" r = self.client.get(url) self.assertEqual(r.status_code, 200) data = r.json() self.assertEqual(data["error"], True) url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=25:99&timezone=UTC" r = self.client.get(url) self.assertEqual(r.status_code, 200) data = r.json() self.assertEqual(data["error"], True) url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=10:00am&timezone=UTC" r = self.client.get(url) self.assertEqual(r.status_code, 200) data = r.json() self.assertEqual(data["error"], True) # test good query url = urlreverse('ietf.meeting.views.ajax_get_utc') + "?date=2016-1-1&time=12:00&timezone=America/Los_Angeles" r = self.client.get(url) self.assertEqual(r.status_code, 200) data = r.json() self.assertIn('timezone', data) self.assertIn('time', data) self.assertIn('utc', data) self.assertNotIn('error', data) self.assertEqual(data['utc'], '20:00') class FloorPlanTests(TestCase): def setUp(self): pass def tearDown(self): pass def test_floor_plan_page(self): make_meeting_test_data() meeting = Meeting.objects.filter(type_id='ietf').order_by('id').last() floorplan = FloorPlanFactory.create(meeting=meeting) url = urlreverse('ietf.meeting.views.floor_plan') r = self.client.get(url) self.assertEqual(r.status_code, 200) url = urlreverse('ietf.meeting.views.floor_plan', kwargs={'floor': xslugify(floorplan.name)} ) r = self.client.get(url) self.assertEqual(r.status_code, 200) class IphoneAppJsonTests(TestCase): def setUp(self): pass def tearDown(self): pass def test_iphone_app_json_interim(self): make_interim_test_data() meeting = Meeting.objects.filter(type_id='interim').order_by('id').last() url = urlreverse('ietf.meeting.views.agenda_json',kwargs={'num':meeting.number}) r = self.client.get(url) self.assertEqual(r.status_code,200) data = r.json() self.assertIn(meeting.number, data.keys()) jsessions = [ s for s in data[meeting.number] if s['objtype'] == 'session' ] msessions = meeting.session_set.exclude(type__in=['lead','offagenda','break','reg']) self.assertEqual(len(jsessions), msessions.count()) for s in jsessions: self.assertTrue(msessions.filter(group__acronym=s['group']['acronym']).exists()) def test_iphone_app_json(self): make_meeting_test_data() meeting = Meeting.objects.filter(type_id='ietf').order_by('id').last() floorplan = FloorPlanFactory.create(meeting=meeting) for room in meeting.room_set.all(): room.floorplan = floorplan room.x1 = random.randint(0,100) room.y1 = random.randint(0,100) room.x2 = random.randint(0,100) room.y2 = random.randint(0,100) room.save() url = urlreverse('ietf.meeting.views.agenda_json',kwargs={'num':meeting.number}) r = self.client.get(url) self.assertEqual(r.status_code,200) data = r.json() self.assertIn(meeting.number, data.keys()) jsessions = [ s for s in data[meeting.number] if s['objtype'] == 'session' ] msessions = meeting.session_set.exclude(type__in=['lead','offagenda','break','reg']) self.assertEqual(len(jsessions), msessions.count()) for s in jsessions: self.assertTrue(msessions.filter(group__acronym=s['group']['acronym']).exists()) class FinalizeProceedingsTests(TestCase): @patch('urllib.request.urlopen') def test_finalize_proceedings(self, mock_urlopen): mock_urlopen.return_value = BytesIO(b'[{"LastName":"Smith","FirstName":"John","Company":"ABC","Country":"US"}]') make_meeting_test_data() meeting = Meeting.objects.filter(type_id='ietf').order_by('id').last() meeting.session_set.filter(group__acronym='mars').first().sessionpresentation_set.create(document=Document.objects.filter(type='draft').first(),rev=None) url = urlreverse('ietf.meeting.views.finalize_proceedings',kwargs={'num':meeting.number}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertEqual(meeting.proceedings_final,False) self.assertEqual(meeting.session_set.filter(group__acronym="mars").first().sessionpresentation_set.filter(document__type="draft").first().rev,None) r = self.client.post(url,{'finalize':1}) self.assertEqual(r.status_code, 302) meeting = Meeting.objects.get(pk=meeting.pk) self.assertEqual(meeting.proceedings_final,True) self.assertEqual(meeting.session_set.filter(group__acronym="mars").first().sessionpresentation_set.filter(document__type="draft").first().rev,'00') class MaterialsTests(TestCase): def setUp(self): self.materials_dir = self.tempdir('materials') self.staging_dir = self.tempdir('staging') if not os.path.exists(self.materials_dir): os.mkdir(self.materials_dir) self.saved_agenda_path = settings.AGENDA_PATH settings.AGENDA_PATH = self.materials_dir self.saved_staging_path = settings.SLIDE_STAGING_PATH settings.SLIDE_STAGING_PATH = self.staging_dir def tearDown(self): settings.AGENDA_PATH = self.saved_agenda_path settings.SLIDE_STAGING_PATH = self.saved_staging_path shutil.rmtree(self.materials_dir) shutil.rmtree(self.staging_dir) def crawl_materials(self, url, top): seen = set() def follow(url): seen.add(url) r = self.client.get(url) self.assertEqual(r.status_code, 200) if not ('.' in url and url.rsplit('.', 1)[1] in ['tgz', 'pdf', ]): if r.content: page = unicontent(r) soup = BeautifulSoup(page, 'html.parser') for a in soup('a'): href = a.get('href') path = urlparse(href).path if (path and path not in seen and path.startswith(top)): follow(path) follow(url) def test_upload_bluesheets(self): session = SessionFactory(meeting__type_id='ietf') url = urlreverse('ietf.meeting.views.upload_session_bluesheets',kwargs={'num':session.meeting.number,'session_id':session.id}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("title"))) self.assertFalse(session.sessionpresentation_set.exists()) test_file = StringIO('%PDF-1.4\n%âãÏÓ\nthis is some text for a test') test_file.name = "not_really.pdf" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 302) bs_doc = session.sessionpresentation_set.filter(document__type_id='bluesheets').first().document self.assertEqual(bs_doc.rev,'00') r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Revise', str(q("title"))) test_file = StringIO('%PDF-1.4\n%âãÏÓ\nthis is some different text for a test') test_file.name = "also_not_really.pdf" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 302) bs_doc = Document.objects.get(pk=bs_doc.pk) self.assertEqual(bs_doc.rev,'01') def test_upload_bluesheets_chair_access(self): make_meeting_test_data() mars = Group.objects.get(acronym='mars') session=SessionFactory(meeting__type_id='ietf',group=mars) url = urlreverse('ietf.meeting.views.upload_session_bluesheets',kwargs={'num':session.meeting.number,'session_id':session.id}) self.client.login(username="marschairman", password="marschairman+password") r = self.client.get(url) self.assertEqual(r.status_code, 403) def test_upload_bluesheets_interim(self): session=SessionFactory(meeting__type_id='interim') url = urlreverse('ietf.meeting.views.upload_session_bluesheets',kwargs={'num':session.meeting.number,'session_id':session.id}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("title"))) self.assertFalse(session.sessionpresentation_set.exists()) test_file = StringIO('%PDF-1.4\n%âãÏÓ\nthis is some text for a test') test_file.name = "not_really.pdf" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 302) bs_doc = session.sessionpresentation_set.filter(document__type_id='bluesheets').first().document self.assertEqual(bs_doc.rev,'00') def test_upload_bluesheets_interim_chair_access(self): make_meeting_test_data() mars = Group.objects.get(acronym='mars') session=SessionFactory(meeting__type_id='interim',group=mars, meeting__date = datetime.date.today()) url = urlreverse('ietf.meeting.views.upload_session_bluesheets',kwargs={'num':session.meeting.number,'session_id':session.id}) self.client.login(username="marschairman", password="marschairman+password") r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("title"))) def test_upload_minutes_agenda(self): for doctype in ('minutes','agenda'): session = SessionFactory(meeting__type_id='ietf') if doctype == 'minutes': url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id}) else: url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id}) self.client.logout() login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("Title"))) self.assertFalse(session.sessionpresentation_set.exists()) self.assertFalse(q('form input[type="checkbox"]')) session2 = SessionFactory(meeting=session.meeting,group=session.group) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('form input[type="checkbox"]')) test_file = BytesIO(b'this is some text for a test') test_file.name = "not_really.json" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('form .has-error')) test_file = BytesIO(b'this is some text for a test'*1510000) test_file.name = "not_really.pdf" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('form .has-error')) test_file = BytesIO(b'') test_file.name = "not_really.html" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('form .has-error')) # Test html sanitization test_file = BytesIO(b'Title

    Title

    Some text
    ') test_file.name = "some.html" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 302) doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document self.assertEqual(doc.rev,'00') text = doc.text() self.assertIn('Some text', text) self.assertNotIn('
    ', text) self.assertIn('charset="utf-8"', text) # txt upload test_file = BytesIO(b'This is some text for a test, with the word\nvirtual at the beginning of a line.') test_file.name = "some.txt" r = self.client.post(url,dict(file=test_file,apply_to_all=False)) self.assertEqual(r.status_code, 302) doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document self.assertEqual(doc.rev,'01') self.assertFalse(session2.sessionpresentation_set.filter(document__type_id=doctype)) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Revise', str(q("Title"))) test_file = BytesIO(b'this is some different text for a test') test_file.name = "also_some.txt" r = self.client.post(url,dict(file=test_file,apply_to_all=True)) self.assertEqual(r.status_code, 302) doc = Document.objects.get(pk=doc.pk) self.assertEqual(doc.rev,'02') self.assertTrue(session2.sessionpresentation_set.filter(document__type_id=doctype)) # Test bad encoding test_file = BytesIO('

    Title

    Some\x93text
    '.encode('latin1')) test_file.name = "some.html" r = self.client.post(url,dict(file=test_file)) self.assertContains(r, 'Could not identify the file encoding') doc = Document.objects.get(pk=doc.pk) self.assertEqual(doc.rev,'02') # Verify that we don't have dead links url = url=urlreverse('ietf.meeting.views.session_details', kwargs={'num':session.meeting.number, 'acronym': session.group.acronym}) top = '/meeting/%s/' % session.meeting.number self.crawl_materials(url=url, top=top) def test_upload_minutes_agenda_unscheduled(self): for doctype in ('minutes','agenda'): session = SessionFactory(meeting__type_id='ietf', add_to_schedule=False) if doctype == 'minutes': url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id}) else: url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id}) self.client.logout() login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("Title"))) self.assertFalse(session.sessionpresentation_set.exists()) self.assertFalse(q('form input[type="checkbox"]')) test_file = BytesIO(b'this is some text for a test') test_file.name = "not_really.txt" r = self.client.post(url,dict(file=test_file,apply_to_all=False)) self.assertEqual(r.status_code, 410) @override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True) def test_upload_minutes_agenda_interim(self): session=SessionFactory(meeting__type_id='interim') for doctype in ('minutes','agenda'): if doctype=='minutes': url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id}) else: url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id}) self.client.logout() login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("title"))) self.assertFalse(session.sessionpresentation_set.filter(document__type_id=doctype)) test_file = BytesIO(b'this is some text for a test') test_file.name = "not_really.txt" r = self.client.post(url,dict(file=test_file)) self.assertEqual(r.status_code, 302) doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document self.assertEqual(doc.rev,'00') # Verify that we don't have dead links url = url=urlreverse('ietf.meeting.views.session_details', kwargs={'num':session.meeting.number, 'acronym': session.group.acronym}) top = '/meeting/%s/' % session.meeting.number self.crawl_materials(url=url, top=top) def test_upload_slides(self): session1 = SessionFactory(meeting__type_id='ietf') session2 = SessionFactory(meeting=session1.meeting,group=session1.group) url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session1.meeting.number,'session_id':session1.id}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("title"))) self.assertFalse(session1.sessionpresentation_set.filter(document__type_id='slides')) test_file = BytesIO(b'this is not really a slide') test_file.name = 'not_really.txt' r = self.client.post(url,dict(file=test_file,title='a test slide file',apply_to_all=True)) self.assertEqual(r.status_code, 302) self.assertEqual(session1.sessionpresentation_set.count(),1) self.assertEqual(session2.sessionpresentation_set.count(),1) sp = session2.sessionpresentation_set.first() self.assertEqual(sp.document.name, 'slides-%s-%s-a-test-slide-file' % (session1.meeting.number,session1.group.acronym ) ) self.assertEqual(sp.order,1) url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session2.meeting.number,'session_id':session2.id}) test_file = BytesIO(b'some other thing still not slidelike') test_file.name = 'also_not_really.txt' r = self.client.post(url,dict(file=test_file,title='a different slide file',apply_to_all=False)) self.assertEqual(r.status_code, 302) self.assertEqual(session1.sessionpresentation_set.count(),1) self.assertEqual(session2.sessionpresentation_set.count(),2) sp = session2.sessionpresentation_set.get(document__name__endswith='-a-different-slide-file') self.assertEqual(sp.order,2) self.assertEqual(sp.rev,'00') self.assertEqual(sp.document.rev,'00') url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session2.meeting.number,'session_id':session2.id,'name':session2.sessionpresentation_set.get(order=2).document.name}) r = self.client.get(url) self.assertTrue(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Revise', str(q("title"))) test_file = BytesIO(b'new content for the second slide deck') test_file.name = 'doesnotmatter.txt' r = self.client.post(url,dict(file=test_file,title='rename the presentation',apply_to_all=False)) self.assertEqual(r.status_code, 302) self.assertEqual(session1.sessionpresentation_set.count(),1) self.assertEqual(session2.sessionpresentation_set.count(),2) sp = session2.sessionpresentation_set.get(order=2) self.assertEqual(sp.rev,'01') self.assertEqual(sp.document.rev,'01') def test_upload_slide_title_bad_unicode(self): session1 = SessionFactory(meeting__type_id='ietf') url = urlreverse('ietf.meeting.views.upload_session_slides',kwargs={'num':session1.meeting.number,'session_id':session1.id}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertIn('Upload', str(q("title"))) self.assertFalse(session1.sessionpresentation_set.filter(document__type_id='slides')) test_file = BytesIO(b'this is not really a slide') test_file.name = 'not_really.txt' r = self.client.post(url,dict(file=test_file,title='title with bad character \U0001fabc ')) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('form .has-error')) self.assertIn("Unicode BMP", q('form .has-error div').text()) def test_remove_sessionpresentation(self): session = SessionFactory(meeting__type_id='ietf') doc = DocumentFactory(type_id='slides') session.sessionpresentation_set.create(document=doc) url = urlreverse('ietf.meeting.views.remove_sessionpresentation',kwargs={'num':session.meeting.number,'session_id':session.id,'name':'no-such-doc'}) response = self.client.get(url) self.assertEqual(response.status_code, 404) url = urlreverse('ietf.meeting.views.remove_sessionpresentation',kwargs={'num':session.meeting.number,'session_id':0,'name':doc.name}) response = self.client.get(url) self.assertEqual(response.status_code, 404) url = urlreverse('ietf.meeting.views.remove_sessionpresentation',kwargs={'num':session.meeting.number,'session_id':session.id,'name':doc.name}) login_testing_unauthorized(self,"secretary",url) response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(1,session.sessionpresentation_set.count()) response = self.client.post(url,{'remove_session':''}) self.assertEqual(response.status_code, 302) self.assertEqual(0,session.sessionpresentation_set.count()) self.assertEqual(2,doc.docevent_set.count()) def test_propose_session_slides(self): for type_id in ['ietf','interim']: session = SessionFactory(meeting__type_id=type_id) chair = RoleFactory(group=session.group,name_id='chair').person session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20)) newperson = PersonFactory() session_overview_url = urlreverse('ietf.meeting.views.session_details',kwargs={'num':session.meeting.number,'acronym':session.group.acronym}) propose_url = urlreverse('ietf.meeting.views.propose_session_slides', kwargs={'session_id':session.pk, 'num': session.meeting.number}) r = self.client.get(session_overview_url) self.assertEqual(r.status_code,200) q = PyQuery(r.content) self.assertFalse(q('#uploadslides')) self.assertFalse(q('#proposeslides')) self.client.login(username=newperson.user.username,password=newperson.user.username+"+password") r = self.client.get(session_overview_url) self.assertEqual(r.status_code,200) q = PyQuery(r.content) self.assertTrue(q('#proposeslides')) self.client.logout() login_testing_unauthorized(self,newperson.user.username,propose_url) r = self.client.get(propose_url) self.assertEqual(r.status_code,200) test_file = BytesIO(b'this is not really a slide') test_file.name = 'not_really.txt' empty_outbox() r = self.client.post(propose_url,dict(file=test_file,title='a test slide file',apply_to_all=True)) self.assertEqual(r.status_code, 302) session = Session.objects.get(pk=session.pk) self.assertEqual(session.slidesubmission_set.count(),1) self.assertEqual(len(outbox),1) r = self.client.get(session_overview_url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('#proposedslidelist p')), 1) SlideSubmissionFactory(session = session) self.client.logout() self.client.login(username=chair.user.username, password=chair.user.username+"+password") r = self.client.get(session_overview_url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('#proposedslidelist p')), 2) self.client.logout() def test_disapprove_proposed_slides(self): submission = SlideSubmissionFactory() submission.session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20)) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(), 1) chair = RoleFactory(group=submission.session.group,name_id='chair').person url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number}) login_testing_unauthorized(self, chair.user.username, url) r = self.client.get(url) self.assertEqual(r.status_code,200) r = self.client.post(url,dict(title='some title',disapprove="disapprove")) self.assertEqual(r.status_code,302) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'rejected').count(), 1) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(), 0) r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertContains(r, "These slides have already been rejected") def test_approve_proposed_slides(self): submission = SlideSubmissionFactory() session = submission.session session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20)) chair = RoleFactory(group=submission.session.group,name_id='chair').person url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number}) login_testing_unauthorized(self, chair.user.username, url) self.assertEqual(submission.status_id, 'pending') self.assertIsNone(submission.doc) r = self.client.get(url) self.assertEqual(r.status_code,200) r = self.client.post(url,dict(title='different title',approve='approve')) self.assertEqual(r.status_code,302) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(), 0) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'approved').count(), 1) submission = SlideSubmission.objects.get(id = submission.id) self.assertEqual(submission.status_id, 'approved') self.assertIsNotNone(submission.doc) self.assertEqual(session.sessionpresentation_set.count(),1) self.assertEqual(session.sessionpresentation_set.first().document.title,'different title') r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertContains(r, "These slides have already been approved") def test_approve_proposed_slides_multisession_apply_one(self): submission = SlideSubmissionFactory(session__meeting__type_id='ietf') session1 = submission.session session2 = SessionFactory(group=submission.session.group, meeting=submission.session.meeting) submission.session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20)) chair = RoleFactory(group=submission.session.group,name_id='chair').person url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number}) login_testing_unauthorized(self, chair.user.username, url) r = self.client.get(url) self.assertEqual(r.status_code,200) q = PyQuery(r.content) self.assertTrue(q('#id_apply_to_all')) r = self.client.post(url,dict(title='yet another title',approve='approve')) self.assertEqual(r.status_code,302) self.assertEqual(session1.sessionpresentation_set.count(),1) self.assertEqual(session2.sessionpresentation_set.count(),0) def test_approve_proposed_slides_multisession_apply_all(self): submission = SlideSubmissionFactory(session__meeting__type_id='ietf') session1 = submission.session session2 = SessionFactory(group=submission.session.group, meeting=submission.session.meeting) submission.session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20)) chair = RoleFactory(group=submission.session.group,name_id='chair').person url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number}) login_testing_unauthorized(self, chair.user.username, url) r = self.client.get(url) self.assertEqual(r.status_code,200) r = self.client.post(url,dict(title='yet another title',apply_to_all=1,approve='approve')) self.assertEqual(r.status_code,302) self.assertEqual(session1.sessionpresentation_set.count(),1) self.assertEqual(session2.sessionpresentation_set.count(),1) def test_submit_and_approve_multiple_versions(self): session = SessionFactory(meeting__type_id='ietf') chair = RoleFactory(group=session.group,name_id='chair').person session.meeting.importantdate_set.create(name_id='revsub',date=datetime.date.today()+datetime.timedelta(days=20)) newperson = PersonFactory() propose_url = urlreverse('ietf.meeting.views.propose_session_slides', kwargs={'session_id':session.pk, 'num': session.meeting.number}) login_testing_unauthorized(self,newperson.user.username,propose_url) test_file = BytesIO(b'this is not really a slide') test_file.name = 'not_really.txt' r = self.client.post(propose_url,dict(file=test_file,title='a test slide file',apply_to_all=True)) self.assertEqual(r.status_code, 302) self.client.logout() submission = SlideSubmission.objects.get(session = session) approve_url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':submission.pk,'num':submission.session.meeting.number}) login_testing_unauthorized(self, chair.user.username, approve_url) r = self.client.post(approve_url,dict(title=submission.title,approve='approve')) self.assertEqual(r.status_code,302) self.client.logout() self.assertEqual(session.sessionpresentation_set.first().document.rev,'00') login_testing_unauthorized(self,newperson.user.username,propose_url) test_file = BytesIO(b'this is not really a slide, but it is another version of it') test_file.name = 'not_really.txt' r = self.client.post(propose_url,dict(file=test_file,title='a test slide file',apply_to_all=True)) self.assertEqual(r.status_code, 302) test_file = BytesIO(b'this is not really a slide, but it is third version of it') test_file.name = 'not_really.txt' r = self.client.post(propose_url,dict(file=test_file,title='a test slide file',apply_to_all=True)) self.assertEqual(r.status_code, 302) self.client.logout() (first_submission, second_submission) = SlideSubmission.objects.filter(session=session, status__slug = 'pending').order_by('id') approve_url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':second_submission.pk,'num':second_submission.session.meeting.number}) login_testing_unauthorized(self, chair.user.username, approve_url) r = self.client.post(approve_url,dict(title=submission.title,approve='approve')) self.assertEqual(r.status_code,302) disapprove_url = urlreverse('ietf.meeting.views.approve_proposed_slides', kwargs={'slidesubmission_id':first_submission.pk,'num':first_submission.session.meeting.number}) r = self.client.post(disapprove_url,dict(title='some title',disapprove="disapprove")) self.assertEqual(r.status_code,302) self.client.logout() self.assertEqual(SlideSubmission.objects.filter(status__slug = 'pending').count(),0) self.assertEqual(SlideSubmission.objects.filter(status__slug = 'rejected').count(),1) self.assertEqual(session.sessionpresentation_set.first().document.rev,'01') path = os.path.join(submission.session.meeting.get_materials_path(),'slides') filename = os.path.join(path,session.sessionpresentation_set.first().document.name+'-01.txt') self.assertTrue(os.path.exists(filename)) contents = io.open(filename,'r').read() self.assertIn('third version', contents) class SessionTests(TestCase): def test_meeting_requests(self): meeting = MeetingFactory(type_id='ietf') area = GroupFactory(type_id='area') requested_session = SessionFactory(meeting=meeting,group__parent=area,status_id='schedw',add_to_schedule=False) not_meeting = SessionFactory(meeting=meeting,group__parent=area,status_id='notmeet',add_to_schedule=False) url = urlreverse('ietf.meeting.views.meeting_requests',kwargs={'num':meeting.number}) r = self.client.get(url) self.assertContains(r, requested_session.group.acronym) self.assertContains(r, not_meeting.group.acronym) def test_request_minutes(self): meeting = MeetingFactory(type_id='ietf') area = GroupFactory(type_id='area') has_minutes = SessionFactory(meeting=meeting,group__parent=area) has_no_minutes = SessionFactory(meeting=meeting,group__parent=area) SessionPresentation.objects.create(session=has_minutes,document=DocumentFactory(type_id='minutes')) empty_outbox() url = urlreverse('ietf.meeting.views.request_minutes',kwargs={'num':meeting.number}) login_testing_unauthorized(self,"secretary",url) r = self.client.get(url) self.assertNotContains(r, has_minutes.group.acronym.upper()) self.assertContains(r, has_no_minutes.group.acronym.upper()) r = self.client.post(url,{'to':'wgchairs@ietf.org', 'cc': 'irsg@irtf.org', 'subject': 'I changed the subject', 'body': 'corpus', }) self.assertEqual(r.status_code,302) self.assertEqual(len(outbox),1) class HasMeetingsTests(TestCase): def setUp(self): self.materials_dir = self.tempdir('materials') # self.saved_agenda_path = settings.AGENDA_PATH # settings.AGENDA_PATH = self.materials_dir def tearDown(self): shutil.rmtree(self.materials_dir) # settings.AGENDA_PATH = self.saved_agenda_path def do_request_interim(self, url, group, user, meeting_count): login_testing_unauthorized(self,user.username, url) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('#id_group option[value="%d"]'%group.pk)) date = datetime.date.today() + datetime.timedelta(days=30+meeting_count) time = datetime.datetime.now().time().replace(microsecond=0,second=0) remote_instructions = 'Use webex' agenda = 'Intro. Slides. Discuss.' agenda_note = 'On second level' meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count() next_num = "%02d" % (meeting_count+1) data = {'group':group.pk, 'meeting_type':'single', 'city':'', 'country':'', 'time_zone':'UTC', 'session_set-0-date':date.strftime("%Y-%m-%d"), 'session_set-0-time':time.strftime('%H:%M'), 'session_set-0-requested_duration':'03:00:00', 'session_set-0-remote_instructions':remote_instructions, 'session_set-0-agenda':agenda, 'session_set-0-agenda_note':agenda_note, 'session_set-TOTAL_FORMS':1, 'session_set-INITIAL_FORMS':0, 'session_set-MIN_NUM_FORMS':0, 'session_set-MAX_NUM_FORMS':1000} empty_outbox() r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data) self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming')) meeting = Meeting.objects.order_by('id').last() self.assertEqual(meeting.type_id,'interim') self.assertEqual(meeting.date,date) self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num)) self.assertTrue(len(outbox)>0) self.assertIn('interim approved',outbox[0]["Subject"]) self.assertIn(user.person.email().address,outbox[0]["To"]) self.client.logout() def create_role_for_authrole(self, authrole): role = None if authrole == 'Secretariat': role = RoleFactory.create(group__acronym='secretariat',name_id='secr') elif authrole == 'Area Director': role = RoleFactory.create(name_id='ad', group__type_id='area') elif authrole == 'IAB': role = RoleFactory.create(name_id='member', group__acronym='iab') elif authrole == 'IRTF Chair': role = RoleFactory.create(name_id='chair', group__acronym='irtf') if role is None: self.assertIsNone("Can't test authrole:"+authrole) self.assertNotEqual(role, None) return role def test_can_request_interim(self): url = urlreverse('ietf.meeting.views.interim_request') for gf in GroupFeatures.objects.filter(has_meetings=True): meeting_count = 0 for role in gf.groupman_roles: role = RoleFactory(group__type_id=gf.type_id, name_id=role) self.do_request_interim(url, role.group, role.person.user, meeting_count) for authrole in gf.groupman_authroles: group = GroupFactory(type_id=gf.type_id) role = self.create_role_for_authrole(authrole) self.do_request_interim(url, group, role.person.user, 0) def test_cannot_request_interim(self): url = urlreverse('ietf.meeting.views.interim_request') self.client.login(username='secretary', password='secretary+password') nomeetings = [] for gf in GroupFeatures.objects.exclude(has_meetings=True): nomeetings.append(GroupFactory(type_id=gf.type_id)) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) for group in nomeetings: self.assertFalse(q('#id_group option[value="%d"]'%group.pk)) self.client.logout() all_role_names = set(RoleName.objects.values_list('slug',flat=True)) for gf in GroupFeatures.objects.filter(has_meetings=True): for role_name in all_role_names - set(gf.groupman_roles): role = RoleFactory(group__type_id=gf.type_id,name_id=role_name) self.client.login(username=role.person.user.username, password=role.person.user.username+'+password') r = self.client.get(url) self.assertEqual(r.status_code, 403) self.client.logout() def test_appears_on_upcoming(self): url = urlreverse('ietf.meeting.views.upcoming') sessions=[] for gf in GroupFeatures.objects.filter(has_meetings=True): session = SessionFactory( group__type_id = gf.type_id, meeting__type_id='interim', meeting__date = datetime.datetime.today()+datetime.timedelta(days=30), status_id='sched', ) sessions.append(session) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) for session in sessions: self.assertIn(session.meeting.number, q('.interim-meeting-link').text()) def test_appears_on_pending(self): url = urlreverse('ietf.meeting.views.interim_pending') sessions=[] for gf in GroupFeatures.objects.filter(has_meetings=True): group = GroupFactory(type_id=gf.type_id) meeting_date = datetime.datetime.today() + datetime.timedelta(days=30) session = SessionFactory( group=group, meeting__type_id='interim', meeting__date = meeting_date, meeting__number = 'interim-%d-%s-00'%(meeting_date.year,group.acronym), status_id='apprw', ) sessions.append(session) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) for session in sessions: self.assertIn(session.meeting.number, q('.interim-meeting-link').text()) def test_appears_on_announce(self): url = urlreverse('ietf.meeting.views.interim_announce') sessions=[] for gf in GroupFeatures.objects.filter(has_meetings=True): group = GroupFactory(type_id=gf.type_id) meeting_date = datetime.datetime.today() + datetime.timedelta(days=30) session = SessionFactory( group=group, meeting__type_id='interim', meeting__date = meeting_date, meeting__number = 'interim-%d-%s-00'%(meeting_date.year,group.acronym), status_id='scheda', ) sessions.append(session) r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) for session in sessions: self.assertIn(session.meeting.number, q('.interim-meeting-link').text()) class AgendaFilterTests(TestCase): """Tests for the AgendaFilter template""" def test_agenda_width_scale_filter(self): """Test calculation of UI column width by agenda_width_scale filter""" template = Template('{% load agenda_filter_tags %}{{ categories|agenda_width_scale:spacing }}') # Should get '1' as min value when input is empty context = Context({'categories': [], 'spacing': 7}) self.assertEqual(template.render(context), '1') # 3 columns, no spacers context = Context({'categories': [range(3)], 'spacing': 7}) self.assertEqual(template.render(context), '21') # 6 columns, 1 spacer context = Context({'categories': [range(3), range(3)], 'spacing': 7}) self.assertEqual(template.render(context), '43') # 10 columns, 2 spacers context = Context({'categories': [range(3), range(3), range(4)], 'spacing': 7}) self.assertEqual(template.render(context), '72') # 10 columns, 2 spacers, different spacer scale context = Context({'categories': [range(3), range(3), range(4)], 'spacing': 5}) self.assertEqual(template.render(context), '52') def test_agenda_filter_template(self): """Test rendering of input data by the agenda filter template""" def _assert_button_ok(btn, expected_label=None, expected_filter_item=None, expected_filter_keywords=None): """Test button properties""" self.assertIn(btn.text(), expected_label) self.assertEqual(btn.attr('data-filter-item'), expected_filter_item) self.assertEqual(btn.attr('data-filter-keywords'), expected_filter_keywords) template = Template('{% include "meeting/agenda_filter.html" %}') # Test with/without custom button text context = Context({'customize_button_text': None, 'filter_categories': []}) q = PyQuery(template.render(context)) self.assertIn('Customize...', q('h4.panel-title').text()) self.assertEqual(q('table'), []) # no filter_categories, so no button table context['customize_button_text'] = 'My custom text...' q = PyQuery(template.render(context)) self.assertIn(context['customize_button_text'], q('h4.panel-title').text()) self.assertEqual(q('table'), []) # no filter_categories, so no button table # Now add a non-trivial set of filters context['filter_categories'] = [ [ # first category dict( label='area0', keyword='keyword0', children=[ dict( label='child00', keyword='keyword00', is_bof=False, ), dict( label='child01', keyword='keyword01', is_bof=True, ), ]), dict( label='area1', keyword='keyword1', children=[ dict( label='child10', keyword='keyword10', is_bof=False, ), dict( label='child11', keyword='keyword11', is_bof=True, ), ]), ], [ # second category dict( label='area2', keyword='keyword2', children=[ dict( label='child20', keyword='keyword20', is_bof=True, ), dict( label='child21', keyword='keyword21', is_bof=False, ), ]), ], [ # third category dict( label=None, keyword=None, children=[ dict( label='child30', keyword='keyword30', is_bof=False, ), dict( label='child31', keyword='keyword31', is_bof=True, ), ]), ], ] q = PyQuery(template.render(context)) self.assertIn(context['customize_button_text'], q('h4.panel-title').text()) self.assertNotEqual(q('table'), []) # should now have table # Check that buttons are present for the expected things header_row = q('thead tr') self.assertEqual(len(header_row), 1) button_row = q('tbody tr') self.assertEqual(len(button_row), 1) # verify correct headers header_cells = header_row('th') self.assertEqual(len(header_cells), 6) # 4 columns and 2 spacers header_buttons = header_cells('button.pickview') self.assertEqual(len(header_buttons), 3) # last column has blank header, so only 3 # verify buttons button_cells = button_row('td') # area0 _assert_button_ok(header_cells.eq(0)('button.keyword0'), expected_label='area0', expected_filter_item='keyword0') buttons = button_cells.eq(0)('button.pickview') self.assertEqual(len(buttons), 2) # two children _assert_button_ok(buttons('.keyword00'), expected_label='child00', expected_filter_item='keyword00', expected_filter_keywords='keyword0') _assert_button_ok(buttons('.keyword01'), expected_label='child01', expected_filter_item='keyword01', expected_filter_keywords='keyword0,bof') # area1 _assert_button_ok(header_cells.eq(1)('button.keyword1'), expected_label='area1', expected_filter_item='keyword1') buttons = button_cells.eq(1)('button.pickview') self.assertEqual(len(buttons), 2) # two children _assert_button_ok(buttons('.keyword10'), expected_label='child10', expected_filter_item='keyword10', expected_filter_keywords='keyword1') _assert_button_ok(buttons('.keyword11'), expected_label='child11', expected_filter_item='keyword11', expected_filter_keywords='keyword1,bof') # area2 # Skip column index 2, which is a spacer column _assert_button_ok(header_cells.eq(3)('button.keyword2'), expected_label='area2', expected_filter_item='keyword2') buttons = button_cells.eq(3)('button.pickview') self.assertEqual(len(buttons), 2) # two children _assert_button_ok(buttons('.keyword20'), expected_label='child20', expected_filter_item='keyword20', expected_filter_keywords='keyword2,bof') _assert_button_ok(buttons('.keyword21'), expected_label='child21', expected_filter_item='keyword21', expected_filter_keywords='keyword2') # area3 (no label for this one) # Skip column index 4, which is a spacer column self.assertEqual([], header_cells.eq(5)('button')) # no header button buttons = button_cells.eq(5)('button.pickview') self.assertEqual(len(buttons), 2) # two children _assert_button_ok(buttons('.keyword30'), expected_label='child30', expected_filter_item='keyword30', expected_filter_keywords=None) _assert_button_ok(buttons('.keyword31'), expected_label='child31', expected_filter_item='keyword31', expected_filter_keywords='bof')