# Copyright The IETF Trust 2013-2020, All Rights Reserved
# -*- coding: utf-8 -*-


import datetime
import os
import shutil

from pathlib import Path
from pyquery import PyQuery
from io import StringIO

import debug         # pyflakes:ignore

from django.conf import settings
from django.urls import reverse

from ietf.group.models import Group, GroupEvent
from ietf.meeting.factories import MeetingFactory
from ietf.meeting.models import Meeting, Room, TimeSlot, SchedTimeSessAssignment, Session, SchedulingEvent
from ietf.meeting.test_data import make_meeting_test_data
from ietf.name.models import ConstraintName, SessionStatusName
from ietf.person.models import Person
from ietf.secr.meetings.forms import get_times
from ietf.utils.mail import outbox
from ietf.utils.test_utils import TestCase 


class SecrMeetingTestCase(TestCase):
    settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['AGENDA_PATH']
    def setUp(self):
        super().setUp()
        self.bluesheet_dir = self.tempdir('bluesheet')
        self.bluesheet_path = os.path.join(self.bluesheet_dir,'blue_sheet.rtf')
        self.saved_secr_blue_sheet_path = settings.SECR_BLUE_SHEET_PATH
        settings.SECR_BLUE_SHEET_PATH = self.bluesheet_path

        # n.b., the bluesheet upload relies on SECR_PROCEEDINGS_DIR being the same
        # as AGENDA_PATH. This is probably a bug, but may not be worth fixing if
        # the secr app is on the way out.
        self.saved_secr_proceedings_dir = settings.SECR_PROCEEDINGS_DIR
        settings.SECR_PROCEEDINGS_DIR = settings.AGENDA_PATH

    def tearDown(self):
        settings.SECR_PROCEEDINGS_DIR = self.saved_secr_proceedings_dir
        settings.SECR_BLUE_SHEET_PATH = self.saved_secr_blue_sheet_path
        shutil.rmtree(self.bluesheet_dir)
        super().tearDown()

    def test_main(self):
        "Main Test"
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.main')
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'meeting':meeting.number})
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':meeting.number})
        self.assertRedirects(response,url)

    def test_view(self):
        "View Test"
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':meeting.number})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual({option.get('value') for option in q('#id_schedule_selector option:not([value=""])')}, {'base', 'test-schedule', 'test-unofficial-schedule'})
         
    def test_add_meeting(self):
        "Add Meeting"
        meeting = make_meeting_test_data()
        number = int(meeting.number) + 1
        count = Meeting.objects.count()
        url = reverse('ietf.secr.meetings.views.add')
        post_data = dict(
            number=number,city='Toronto',date='2014-07-20',country='CA',
            time_zone='America/New_York',venue_name='Hilton',
            days=6,
            venue_addr='100 First Ave',
            idsubmit_cutoff_day_offset_00=13,
            idsubmit_cutoff_day_offset_01=20,
            idsubmit_cutoff_time_utc     =datetime.timedelta(hours=23, minutes=59, seconds=59),
            idsubmit_cutoff_warning_days =datetime.timedelta(days=21),
            submission_start_day_offset=90,
            submission_cutoff_day_offset=26,
            submission_correction_day_offset=50,
            group_conflict_types=('conflict', 'conflic2', 'key_participant'),
        )

        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Meeting.objects.count(),count + 1)
        new_meeting = Meeting.objects.get(number=number)
        
        self.assertTrue(new_meeting.schedule)
        self.assertEqual(new_meeting.schedule.name, 'sec-retary-1')
        self.assertTrue(new_meeting.schedule.base)
        self.assertEqual(new_meeting.schedule.base.name, 'base')
        self.assertEqual(new_meeting.attendees, None)
        self.assertCountEqual(
            [cn.slug for cn in new_meeting.group_conflict_types.all()],
            post_data['group_conflict_types'],
        )

    def test_add_meeting_default_conflict_types(self):
        """Add meeting should default to same conflict types as previous meeting"""
        def _run_test(mtg):
            url = reverse('ietf.secr.meetings.views.add')
            r = self.client.get(url)
            q = PyQuery(r.content)
            selected_items = q('#id_group_conflict_types input[checked]')
            selected_values = [si.value for si in selected_items]
            expected_values = [cn.slug for cn in mtg.group_conflict_types.all()]
            self.assertCountEqual(selected_values, expected_values)

        self.client.login(username='secretary', password='secretary+password')

        meeting = MeetingFactory(type_id='ietf', group_conflicts=[])  # start with no conflicts selected
        _run_test(meeting)

        # enable one
        meeting.group_conflict_types.add(ConstraintName.objects.filter(is_group_conflict=True).first())
        self.assertEqual(meeting.group_conflict_types.count(), 1)
        _run_test(meeting)

        # enable a few ([::2] selects every other)
        meeting.group_conflict_types.clear()
        for cn in ConstraintName.objects.filter(is_group_conflict=True)[::2]:
            meeting.group_conflict_types.add(cn)
        self.assertGreater(meeting.group_conflict_types.count(), 1)
        _run_test(meeting)

    def test_edit_meeting(self):
        "Edit Meeting"
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.edit_meeting',kwargs={'meeting_id':meeting.number})
        post_data = dict(number=meeting.number,date='2014-07-20',city='Toronto',
                         days=7,
                         idsubmit_cutoff_day_offset_00=13,
                         idsubmit_cutoff_day_offset_01=20,
                         idsubmit_cutoff_time_utc     =datetime.timedelta(hours=23, minutes=59, seconds=59),
                         idsubmit_cutoff_warning_days =datetime.timedelta(days=21),
                         submission_start_day_offset=90,
                         submission_cutoff_day_offset=26,
                         submission_correction_day_offset=50,
                         attendees=1234,
                         group_conflict_types=[
                             cn.slug for cn in ConstraintName.objects.filter(
                                 is_group_conflict=True
                             ).exclude(
                                 meeting=meeting,  # replace original set with those not assigned to the meeting
                             )
                         ]
                    )
        self.assertGreater(len(post_data['group_conflict_types']), 0)  # test should include at least one
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        meeting = Meeting.objects.get(number=meeting.number)
        self.assertEqual(meeting.city,'Toronto')
        self.assertEqual(meeting.attendees,1234)
        self.assertCountEqual(
            [cn.slug for cn in meeting.group_conflict_types.all()],
            post_data['group_conflict_types'],
        )

    def test_blue_sheets_upload(self):
        "Test Bluesheets"
        meeting = make_meeting_test_data()
        (Path(settings.SECR_PROCEEDINGS_DIR) / str(meeting.number) / 'bluesheets').mkdir(parents=True)

        url = reverse('ietf.secr.meetings.views.blue_sheet',kwargs={'meeting_id':meeting.number})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        
        # test upload
        group = Group.objects.filter(type='wg',state='active').first()
        file = StringIO('dummy bluesheet')
        file.name = "bluesheets-%s-%s.pdf" % (meeting.number,group.acronym)
        files = {'file':file}
        response = self.client.post(url, files)
        self.assertEqual(response.status_code, 302)
        path = os.path.join(settings.SECR_PROCEEDINGS_DIR,str(meeting.number),'bluesheets')
        self.assertEqual(len(os.listdir(path)),1)

    def test_blue_sheets_generate(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.blue_sheet_generate',kwargs={'meeting_id':meeting.number})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(os.path.exists(self.bluesheet_path))
        
    def test_notifications(self):
        "Test Notifications"
        meeting = make_meeting_test_data()
        mars_group = Group.objects.get(acronym='mars')
        ames_group = Group.objects.get(acronym='ames')
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        assert SchedulingEvent.objects.filter(session=ames_stsa.session).order_by('-id')[0].status_id == 'schedw'
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        SchedulingEvent.objects.create(session=mars_stsa.session, status=SessionStatusName.objects.get(slug='appr'), by=Person.objects.get(name="(System)"))
        url = reverse('ietf.secr.meetings.views.notifications',kwargs={'meeting_id':72})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(),'ames, mars')

        # test that only changes since last notification show up
        now = datetime.datetime.now()
        then = datetime.datetime.now()+datetime.timedelta(hours=1)
        person = Person.objects.get(name="(System)")
        GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification',
                                  by=person,desc='sent scheduled notification for %s' % meeting)
        ss = meeting.schedule.assignments.get(session__group=ames_group)
        ss.modified = then
        ss.save()
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(),'ames')
        
        # test post: email goes out, status changed
        mailbox_before = len(outbox)
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(outbox), mailbox_before + 1)
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.session).order_by('-id')[0].status_id, 'sched')
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.session).order_by('-id')[0].status_id, 'sched')

    def test_meetings_rooms(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.rooms',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(len(q("#id_rooms_table tr input[type='checkbox']")),meeting.room_set.count())
        
        # test delete
        # first unschedule sessions so we can delete
        SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.schedule, meeting.schedule.base, meeting.unofficial_schedule]).delete()
        self.client.login(username="secretary", password="secretary+password")
        post_dict = {
            'room-TOTAL_FORMS':  q('input[name="room-TOTAL_FORMS"]').val(),
            'room-INITIAL_FORMS': q('input[name="room-INITIAL_FORMS"]').val(),
        }
        for i in range(meeting.room_set.count()):
            for attr in ['meeting','id','name','capacity','DELETE']:
                key = 'room-%d-%s' % (i,attr)
                post_dict[key] = q('input[name="%s"]' % key).val()
        response = self.client.post(url, post_dict)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(Room.objects.filter(meeting=meeting).count(),0)
        
    def test_meetings_times(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.times',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {
            'day': 0,
            'time':'08:00',
            'duration':'01:00',
            'name':'Test Morning Session'
        }, follow=True)
        self.assertRedirects(response, url)
        self.assertContains(response, 'Test Morning Session')

    def test_meetings_times_delete(self):
        meeting = make_meeting_test_data()
        qs = TimeSlot.objects.filter(meeting=meeting,type='regular')
        before = qs.count()
        expected_deletion_count = qs.filter(time=qs.first().time).count() 
        url = reverse('ietf.secr.meetings.views.times_delete',kwargs={
            'meeting_id':meeting.number,
            'schedule_name':meeting.schedule.name,
            'time':qs.first().time.strftime("%Y:%m:%d:%H:%M")
        })
        redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={
            'meeting_id':meeting.number,
            'schedule_name':meeting.schedule.name
        })
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post':'yes'})
        self.assertRedirects(response, redirect_url)
        after = TimeSlot.objects.filter(meeting=meeting,type='regular').count()
        self.assertEqual(after,before - expected_deletion_count)
        
    def test_meetings_times_edit(self):
        meeting = make_meeting_test_data()
        timeslot = TimeSlot.objects.filter(meeting=meeting,type='regular').first()
        url = reverse('ietf.secr.meetings.views.times_edit',kwargs={
            'meeting_id':72,
            'schedule_name':'test-schedule',
            'time':timeslot.time.strftime("%Y:%m:%d:%H:%M")
        })
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, {
            'day':'1',
            'time':'08:00',
            'duration':'09:00',
            'name':'Testing'
        })
        self.assertEqual(response.status_code, 302)
        self.assertTrue(TimeSlot.objects.filter(meeting=meeting,name='Testing'))
        
    def test_meetings_misc_sessions(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
    
    def test_meetings_misc_session_add_valid(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, {
            'day':'1',
            'time':'08:00',
            'duration':'02:00',
            'name':'Testing',
            'short':'test',
            'purpose_0': 'admin',  # purpose
            'purpose_1':'reg',  # type
            'group':group.pk,
            'location': room.pk,
            'remote_instructions': 'http://webex.com/foobar',
        })
        self.assertRedirects(response, url)
        session = Session.objects.filter(meeting=meeting, name='Testing').first()
        self.assertTrue(session)

        self.assertEqual(session.timeslotassignments.first().timeslot.location, room)

    def test_meetings_misc_session_add_invalid(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, {
            'day':'1',
            'time':'08:00',
            'duration':'10',
            'name':'Testing',
            'short':'test',
            'type':'reg',
            'group':group.pk,
        })
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid format')

    def test_meetings_misc_session_edit(self):
        meeting = make_meeting_test_data()
        session = meeting.session_set.exclude(name='').first()   # get first misc session
        timeslot = session.official_timeslotassignment().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.schedule.name,'slot_id':timeslot.pk})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        new_time = timeslot.time + datetime.timedelta(days=1)
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {
            'name':'IETF Hackathon',
            'short':'hackathon',
            'location':timeslot.location.id,
            'group':session.group.id,
            'time':new_time.strftime('%H:%M'),
            'duration':'01:00',
            'day':'2',
            'purpose_0': 'coding',  # purpose
            'purpose_1': 'other',  # type
            'remote_instructions': 'http://webex.com/foobar',
        })
        self.assertRedirects(response, redirect_url)
        timeslot = session.official_timeslotassignment().timeslot
        self.assertEqual(timeslot.time,new_time)

    def test_meetings_misc_session_delete(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_delete', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name,'slot_id':slot.id})
        target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post':'yes'})
        self.assertRedirects(response, target)
        self.assertFalse(schedule.assignments.filter(timeslot=slot))

    def test_meetings_misc_session_cancel(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_cancel', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name,'slot_id':slot.id})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post':'yes'})
        self.assertRedirects(response, redirect_url)
        session = slot.sessionassignments.filter(schedule=schedule).first().session
        self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'canceled')

    def test_meetings_regular_session_edit(self):
        meeting = make_meeting_test_data()
        session = Session.objects.filter(meeting=meeting,group__acronym='mars').first()
        url = reverse('ietf.secr.meetings.views.regular_session_edit', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'session_id':session.id})
        redirect_url = reverse('ietf.secr.meetings.views.regular_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'agenda_note':'TEST'})
        self.assertRedirects(response, redirect_url)
        session = Session.objects.get(id=session.id)
        self.assertEqual(session.agenda_note, 'TEST')

    # ----------------------
    # Unit Tests
    # -----------------------
    def test_get_times(self):
        meeting = make_meeting_test_data()
        timeslot = meeting.timeslot_set.filter(type='regular').first()
        day = (timeslot.time.weekday() + 1) % 7 + 1  # fix up to match django __week_day filter
        times = get_times(meeting,day)
        values = [ x[0] for x in times ]
        self.assertTrue(times)
        self.assertTrue(timeslot.time.strftime('%H%M') in values)

7b6655b033c467ce88fe4eeecc9787e71ad5baec
<mask token>


class SecrMeetingTestCase(TestCase):
    <mask token>

    def setUp(self):
        super().setUp()
        self.bluesheet_dir = self.tempdir('bluesheet')
        self.bluesheet_path = os.path.join(self.bluesheet_dir, 'blue_sheet.rtf'
            )
        self.saved_secr_blue_sheet_path = settings.SECR_BLUE_SHEET_PATH
        settings.SECR_BLUE_SHEET_PATH = self.bluesheet_path
        self.saved_secr_proceedings_dir = settings.SECR_PROCEEDINGS_DIR
        settings.SECR_PROCEEDINGS_DIR = settings.AGENDA_PATH

    def tearDown(self):
        settings.SECR_PROCEEDINGS_DIR = self.saved_secr_proceedings_dir
        settings.SECR_BLUE_SHEET_PATH = self.saved_secr_blue_sheet_path
        shutil.rmtree(self.bluesheet_dir)
        super().tearDown()
    <mask token>
    <mask token>

    def test_add_meeting(self):
        """Add Meeting"""
        meeting = make_meeting_test_data()
        number = int(meeting.number) + 1
        count = Meeting.objects.count()
        url = reverse('ietf.secr.meetings.views.add')
        post_data = dict(number=number, city='Toronto', date='2014-07-20',
            country='CA', time_zone='America/New_York', venue_name='Hilton',
            days=6, venue_addr='100 First Ave',
            idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_01
            =20, idsubmit_cutoff_time_utc=datetime.timedelta(hours=23,
            minutes=59, seconds=59), idsubmit_cutoff_warning_days=datetime.
            timedelta(days=21), submission_start_day_offset=90,
            submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, group_conflict_types=(
            'conflict', 'conflic2', 'key_participant'))
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Meeting.objects.count(), count + 1)
        new_meeting = Meeting.objects.get(number=number)
        self.assertTrue(new_meeting.schedule)
        self.assertEqual(new_meeting.schedule.name, 'sec-retary-1')
        self.assertTrue(new_meeting.schedule.base)
        self.assertEqual(new_meeting.schedule.base.name, 'base')
        self.assertEqual(new_meeting.attendees, None)
        self.assertCountEqual([cn.slug for cn in new_meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])
    <mask token>

    def test_edit_meeting(self):
        """Edit Meeting"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.edit_meeting', kwargs={
            'meeting_id': meeting.number})
        post_data = dict(number=meeting.number, date='2014-07-20', city=
            'Toronto', days=7, idsubmit_cutoff_day_offset_00=13,
            idsubmit_cutoff_day_offset_01=20, idsubmit_cutoff_time_utc=
            datetime.timedelta(hours=23, minutes=59, seconds=59),
            idsubmit_cutoff_warning_days=datetime.timedelta(days=21),
            submission_start_day_offset=90, submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, attendees=1234,
            group_conflict_types=[cn.slug for cn in ConstraintName.objects.
            filter(is_group_conflict=True).exclude(meeting=meeting)])
        self.assertGreater(len(post_data['group_conflict_types']), 0)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        meeting = Meeting.objects.get(number=meeting.number)
        self.assertEqual(meeting.city, 'Toronto')
        self.assertEqual(meeting.attendees, 1234)
        self.assertCountEqual([cn.slug for cn in meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])

    def test_blue_sheets_upload(self):
        """Test Bluesheets"""
        meeting = make_meeting_test_data()
        (Path(settings.SECR_PROCEEDINGS_DIR) / str(meeting.number) /
            'bluesheets').mkdir(parents=True)
        url = reverse('ietf.secr.meetings.views.blue_sheet', kwargs={
            'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        group = Group.objects.filter(type='wg', state='active').first()
        file = StringIO('dummy bluesheet')
        file.name = 'bluesheets-%s-%s.pdf' % (meeting.number, group.acronym)
        files = {'file': file}
        response = self.client.post(url, files)
        self.assertEqual(response.status_code, 302)
        path = os.path.join(settings.SECR_PROCEEDINGS_DIR, str(meeting.
            number), 'bluesheets')
        self.assertEqual(len(os.listdir(path)), 1)
    <mask token>

    def test_notifications(self):
        """Test Notifications"""
        meeting = make_meeting_test_data()
        mars_group = Group.objects.get(acronym='mars')
        ames_group = Group.objects.get(acronym='ames')
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        assert SchedulingEvent.objects.filter(session=ames_stsa.session
            ).order_by('-id')[0].status_id == 'schedw'
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        SchedulingEvent.objects.create(session=mars_stsa.session, status=
            SessionStatusName.objects.get(slug='appr'), by=Person.objects.
            get(name='(System)'))
        url = reverse('ietf.secr.meetings.views.notifications', kwargs={
            'meeting_id': 72})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames, mars')
        now = datetime.datetime.now()
        then = datetime.datetime.now() + datetime.timedelta(hours=1)
        person = Person.objects.get(name='(System)')
        GroupEvent.objects.create(group=mars_group, time=now, type=
            'sent_notification', by=person, desc=
            'sent scheduled notification for %s' % meeting)
        ss = meeting.schedule.assignments.get(session__group=ames_group)
        ss.modified = then
        ss.save()
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames')
        mailbox_before = len(outbox)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(outbox), mailbox_before + 1)
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.
            session).order_by('-id')[0].status_id, 'sched')
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.
            session).order_by('-id')[0].status_id, 'sched')
    <mask token>
    <mask token>

    def test_meetings_times_delete(self):
        meeting = make_meeting_test_data()
        qs = TimeSlot.objects.filter(meeting=meeting, type='regular')
        before = qs.count()
        expected_deletion_count = qs.filter(time=qs.first().time).count()
        url = reverse('ietf.secr.meetings.views.times_delete', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name, 'time': qs.first().time.strftime('%Y:%m:%d:%H:%M')})
        redirect_url = reverse('ietf.secr.meetings.views.times', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        after = TimeSlot.objects.filter(meeting=meeting, type='regular').count(
            )
        self.assertEqual(after, before - expected_deletion_count)
    <mask token>

    def test_meetings_misc_sessions(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_meetings_misc_session_add_valid(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '02:00', 'name': 'Testing', 'short': 'test',
            'purpose_0': 'admin', 'purpose_1': 'reg', 'group': group.pk,
            'location': room.pk, 'remote_instructions':
            'http://webex.com/foobar'})
        self.assertRedirects(response, url)
        session = Session.objects.filter(meeting=meeting, name='Testing'
            ).first()
        self.assertTrue(session)
        self.assertEqual(session.timeslotassignments.first().timeslot.
            location, room)

    def test_meetings_misc_session_add_invalid(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '10', 'name': 'Testing', 'short': 'test', 'type':
            'reg', 'group': group.pk})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid format')

    def test_meetings_misc_session_edit(self):
        meeting = make_meeting_test_data()
        session = meeting.session_set.exclude(name='').first()
        timeslot = session.official_timeslotassignment().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_edit', kwargs=
            {'meeting_id': 72, 'schedule_name': meeting.schedule.name,
            'slot_id': timeslot.pk})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': 72, 'schedule_name': 'test-schedule'})
        new_time = timeslot.time + datetime.timedelta(days=1)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'name': 'IETF Hackathon', 'short':
            'hackathon', 'location': timeslot.location.id, 'group': session
            .group.id, 'time': new_time.strftime('%H:%M'), 'duration':
            '01:00', 'day': '2', 'purpose_0': 'coding', 'purpose_1':
            'other', 'remote_instructions': 'http://webex.com/foobar'})
        self.assertRedirects(response, redirect_url)
        timeslot = session.official_timeslotassignment().timeslot
        self.assertEqual(timeslot.time, new_time)

    def test_meetings_misc_session_delete(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_delete',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': meeting.number, 'schedule_name': schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, target)
        self.assertFalse(schedule.assignments.filter(timeslot=slot))

    def test_meetings_misc_session_cancel(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_cancel',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        session = slot.sessionassignments.filter(schedule=schedule).first(
            ).session
        self.assertEqual(SchedulingEvent.objects.filter(session=session).
            order_by('-id')[0].status_id, 'canceled')
    <mask token>
    <mask token>

<mask token>


class SecrMeetingTestCase(TestCase):
    <mask token>

    def setUp(self):
        super().setUp()
        self.bluesheet_dir = self.tempdir('bluesheet')
        self.bluesheet_path = os.path.join(self.bluesheet_dir, 'blue_sheet.rtf'
            )
        self.saved_secr_blue_sheet_path = settings.SECR_BLUE_SHEET_PATH
        settings.SECR_BLUE_SHEET_PATH = self.bluesheet_path
        self.saved_secr_proceedings_dir = settings.SECR_PROCEEDINGS_DIR
        settings.SECR_PROCEEDINGS_DIR = settings.AGENDA_PATH

    def tearDown(self):
        settings.SECR_PROCEEDINGS_DIR = self.saved_secr_proceedings_dir
        settings.SECR_BLUE_SHEET_PATH = self.saved_secr_blue_sheet_path
        shutil.rmtree(self.bluesheet_dir)
        super().tearDown()

    def test_main(self):
        """Main Test"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.main')
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'meeting': meeting.number})
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':
            meeting.number})
        self.assertRedirects(response, url)

    def test_view(self):
        """View Test"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':
            meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual({option.get('value') for option in q(
            '#id_schedule_selector option:not([value=""])')}, {'base',
            'test-schedule', 'test-unofficial-schedule'})

    def test_add_meeting(self):
        """Add Meeting"""
        meeting = make_meeting_test_data()
        number = int(meeting.number) + 1
        count = Meeting.objects.count()
        url = reverse('ietf.secr.meetings.views.add')
        post_data = dict(number=number, city='Toronto', date='2014-07-20',
            country='CA', time_zone='America/New_York', venue_name='Hilton',
            days=6, venue_addr='100 First Ave',
            idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_01
            =20, idsubmit_cutoff_time_utc=datetime.timedelta(hours=23,
            minutes=59, seconds=59), idsubmit_cutoff_warning_days=datetime.
            timedelta(days=21), submission_start_day_offset=90,
            submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, group_conflict_types=(
            'conflict', 'conflic2', 'key_participant'))
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Meeting.objects.count(), count + 1)
        new_meeting = Meeting.objects.get(number=number)
        self.assertTrue(new_meeting.schedule)
        self.assertEqual(new_meeting.schedule.name, 'sec-retary-1')
        self.assertTrue(new_meeting.schedule.base)
        self.assertEqual(new_meeting.schedule.base.name, 'base')
        self.assertEqual(new_meeting.attendees, None)
        self.assertCountEqual([cn.slug for cn in new_meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])
    <mask token>

    def test_edit_meeting(self):
        """Edit Meeting"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.edit_meeting', kwargs={
            'meeting_id': meeting.number})
        post_data = dict(number=meeting.number, date='2014-07-20', city=
            'Toronto', days=7, idsubmit_cutoff_day_offset_00=13,
            idsubmit_cutoff_day_offset_01=20, idsubmit_cutoff_time_utc=
            datetime.timedelta(hours=23, minutes=59, seconds=59),
            idsubmit_cutoff_warning_days=datetime.timedelta(days=21),
            submission_start_day_offset=90, submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, attendees=1234,
            group_conflict_types=[cn.slug for cn in ConstraintName.objects.
            filter(is_group_conflict=True).exclude(meeting=meeting)])
        self.assertGreater(len(post_data['group_conflict_types']), 0)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        meeting = Meeting.objects.get(number=meeting.number)
        self.assertEqual(meeting.city, 'Toronto')
        self.assertEqual(meeting.attendees, 1234)
        self.assertCountEqual([cn.slug for cn in meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])

    def test_blue_sheets_upload(self):
        """Test Bluesheets"""
        meeting = make_meeting_test_data()
        (Path(settings.SECR_PROCEEDINGS_DIR) / str(meeting.number) /
            'bluesheets').mkdir(parents=True)
        url = reverse('ietf.secr.meetings.views.blue_sheet', kwargs={
            'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        group = Group.objects.filter(type='wg', state='active').first()
        file = StringIO('dummy bluesheet')
        file.name = 'bluesheets-%s-%s.pdf' % (meeting.number, group.acronym)
        files = {'file': file}
        response = self.client.post(url, files)
        self.assertEqual(response.status_code, 302)
        path = os.path.join(settings.SECR_PROCEEDINGS_DIR, str(meeting.
            number), 'bluesheets')
        self.assertEqual(len(os.listdir(path)), 1)

    def test_blue_sheets_generate(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.blue_sheet_generate',
            kwargs={'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(os.path.exists(self.bluesheet_path))

    def test_notifications(self):
        """Test Notifications"""
        meeting = make_meeting_test_data()
        mars_group = Group.objects.get(acronym='mars')
        ames_group = Group.objects.get(acronym='ames')
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        assert SchedulingEvent.objects.filter(session=ames_stsa.session
            ).order_by('-id')[0].status_id == 'schedw'
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        SchedulingEvent.objects.create(session=mars_stsa.session, status=
            SessionStatusName.objects.get(slug='appr'), by=Person.objects.
            get(name='(System)'))
        url = reverse('ietf.secr.meetings.views.notifications', kwargs={
            'meeting_id': 72})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames, mars')
        now = datetime.datetime.now()
        then = datetime.datetime.now() + datetime.timedelta(hours=1)
        person = Person.objects.get(name='(System)')
        GroupEvent.objects.create(group=mars_group, time=now, type=
            'sent_notification', by=person, desc=
            'sent scheduled notification for %s' % meeting)
        ss = meeting.schedule.assignments.get(session__group=ames_group)
        ss.modified = then
        ss.save()
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames')
        mailbox_before = len(outbox)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(outbox), mailbox_before + 1)
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.
            session).order_by('-id')[0].status_id, 'sched')
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.
            session).order_by('-id')[0].status_id, 'sched')
    <mask token>
    <mask token>

    def test_meetings_times_delete(self):
        meeting = make_meeting_test_data()
        qs = TimeSlot.objects.filter(meeting=meeting, type='regular')
        before = qs.count()
        expected_deletion_count = qs.filter(time=qs.first().time).count()
        url = reverse('ietf.secr.meetings.views.times_delete', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name, 'time': qs.first().time.strftime('%Y:%m:%d:%H:%M')})
        redirect_url = reverse('ietf.secr.meetings.views.times', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        after = TimeSlot.objects.filter(meeting=meeting, type='regular').count(
            )
        self.assertEqual(after, before - expected_deletion_count)
    <mask token>

    def test_meetings_misc_sessions(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_meetings_misc_session_add_valid(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '02:00', 'name': 'Testing', 'short': 'test',
            'purpose_0': 'admin', 'purpose_1': 'reg', 'group': group.pk,
            'location': room.pk, 'remote_instructions':
            'http://webex.com/foobar'})
        self.assertRedirects(response, url)
        session = Session.objects.filter(meeting=meeting, name='Testing'
            ).first()
        self.assertTrue(session)
        self.assertEqual(session.timeslotassignments.first().timeslot.
            location, room)

    def test_meetings_misc_session_add_invalid(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '10', 'name': 'Testing', 'short': 'test', 'type':
            'reg', 'group': group.pk})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid format')

    def test_meetings_misc_session_edit(self):
        meeting = make_meeting_test_data()
        session = meeting.session_set.exclude(name='').first()
        timeslot = session.official_timeslotassignment().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_edit', kwargs=
            {'meeting_id': 72, 'schedule_name': meeting.schedule.name,
            'slot_id': timeslot.pk})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': 72, 'schedule_name': 'test-schedule'})
        new_time = timeslot.time + datetime.timedelta(days=1)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'name': 'IETF Hackathon', 'short':
            'hackathon', 'location': timeslot.location.id, 'group': session
            .group.id, 'time': new_time.strftime('%H:%M'), 'duration':
            '01:00', 'day': '2', 'purpose_0': 'coding', 'purpose_1':
            'other', 'remote_instructions': 'http://webex.com/foobar'})
        self.assertRedirects(response, redirect_url)
        timeslot = session.official_timeslotassignment().timeslot
        self.assertEqual(timeslot.time, new_time)

    def test_meetings_misc_session_delete(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_delete',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': meeting.number, 'schedule_name': schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, target)
        self.assertFalse(schedule.assignments.filter(timeslot=slot))

    def test_meetings_misc_session_cancel(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_cancel',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        session = slot.sessionassignments.filter(schedule=schedule).first(
            ).session
        self.assertEqual(SchedulingEvent.objects.filter(session=session).
            order_by('-id')[0].status_id, 'canceled')

    def test_meetings_regular_session_edit(self):
        meeting = make_meeting_test_data()
        session = Session.objects.filter(meeting=meeting, group__acronym='mars'
            ).first()
        url = reverse('ietf.secr.meetings.views.regular_session_edit',
            kwargs={'meeting_id': meeting.number, 'schedule_name': meeting.
            schedule.name, 'session_id': session.id})
        redirect_url = reverse('ietf.secr.meetings.views.regular_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': meeting.
            schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'agenda_note': 'TEST'})
        self.assertRedirects(response, redirect_url)
        session = Session.objects.get(id=session.id)
        self.assertEqual(session.agenda_note, 'TEST')

    def test_get_times(self):
        meeting = make_meeting_test_data()
        timeslot = meeting.timeslot_set.filter(type='regular').first()
        day = (timeslot.time.weekday() + 1) % 7 + 1
        times = get_times(meeting, day)
        values = [x[0] for x in times]
        self.assertTrue(times)
        self.assertTrue(timeslot.time.strftime('%H%M') in values)

<mask token>


class SecrMeetingTestCase(TestCase):
    <mask token>

    def setUp(self):
        super().setUp()
        self.bluesheet_dir = self.tempdir('bluesheet')
        self.bluesheet_path = os.path.join(self.bluesheet_dir, 'blue_sheet.rtf'
            )
        self.saved_secr_blue_sheet_path = settings.SECR_BLUE_SHEET_PATH
        settings.SECR_BLUE_SHEET_PATH = self.bluesheet_path
        self.saved_secr_proceedings_dir = settings.SECR_PROCEEDINGS_DIR
        settings.SECR_PROCEEDINGS_DIR = settings.AGENDA_PATH

    def tearDown(self):
        settings.SECR_PROCEEDINGS_DIR = self.saved_secr_proceedings_dir
        settings.SECR_BLUE_SHEET_PATH = self.saved_secr_blue_sheet_path
        shutil.rmtree(self.bluesheet_dir)
        super().tearDown()

    def test_main(self):
        """Main Test"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.main')
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'meeting': meeting.number})
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':
            meeting.number})
        self.assertRedirects(response, url)

    def test_view(self):
        """View Test"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':
            meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual({option.get('value') for option in q(
            '#id_schedule_selector option:not([value=""])')}, {'base',
            'test-schedule', 'test-unofficial-schedule'})

    def test_add_meeting(self):
        """Add Meeting"""
        meeting = make_meeting_test_data()
        number = int(meeting.number) + 1
        count = Meeting.objects.count()
        url = reverse('ietf.secr.meetings.views.add')
        post_data = dict(number=number, city='Toronto', date='2014-07-20',
            country='CA', time_zone='America/New_York', venue_name='Hilton',
            days=6, venue_addr='100 First Ave',
            idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_01
            =20, idsubmit_cutoff_time_utc=datetime.timedelta(hours=23,
            minutes=59, seconds=59), idsubmit_cutoff_warning_days=datetime.
            timedelta(days=21), submission_start_day_offset=90,
            submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, group_conflict_types=(
            'conflict', 'conflic2', 'key_participant'))
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Meeting.objects.count(), count + 1)
        new_meeting = Meeting.objects.get(number=number)
        self.assertTrue(new_meeting.schedule)
        self.assertEqual(new_meeting.schedule.name, 'sec-retary-1')
        self.assertTrue(new_meeting.schedule.base)
        self.assertEqual(new_meeting.schedule.base.name, 'base')
        self.assertEqual(new_meeting.attendees, None)
        self.assertCountEqual([cn.slug for cn in new_meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])
    <mask token>

    def test_edit_meeting(self):
        """Edit Meeting"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.edit_meeting', kwargs={
            'meeting_id': meeting.number})
        post_data = dict(number=meeting.number, date='2014-07-20', city=
            'Toronto', days=7, idsubmit_cutoff_day_offset_00=13,
            idsubmit_cutoff_day_offset_01=20, idsubmit_cutoff_time_utc=
            datetime.timedelta(hours=23, minutes=59, seconds=59),
            idsubmit_cutoff_warning_days=datetime.timedelta(days=21),
            submission_start_day_offset=90, submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, attendees=1234,
            group_conflict_types=[cn.slug for cn in ConstraintName.objects.
            filter(is_group_conflict=True).exclude(meeting=meeting)])
        self.assertGreater(len(post_data['group_conflict_types']), 0)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        meeting = Meeting.objects.get(number=meeting.number)
        self.assertEqual(meeting.city, 'Toronto')
        self.assertEqual(meeting.attendees, 1234)
        self.assertCountEqual([cn.slug for cn in meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])

    def test_blue_sheets_upload(self):
        """Test Bluesheets"""
        meeting = make_meeting_test_data()
        (Path(settings.SECR_PROCEEDINGS_DIR) / str(meeting.number) /
            'bluesheets').mkdir(parents=True)
        url = reverse('ietf.secr.meetings.views.blue_sheet', kwargs={
            'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        group = Group.objects.filter(type='wg', state='active').first()
        file = StringIO('dummy bluesheet')
        file.name = 'bluesheets-%s-%s.pdf' % (meeting.number, group.acronym)
        files = {'file': file}
        response = self.client.post(url, files)
        self.assertEqual(response.status_code, 302)
        path = os.path.join(settings.SECR_PROCEEDINGS_DIR, str(meeting.
            number), 'bluesheets')
        self.assertEqual(len(os.listdir(path)), 1)

    def test_blue_sheets_generate(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.blue_sheet_generate',
            kwargs={'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(os.path.exists(self.bluesheet_path))

    def test_notifications(self):
        """Test Notifications"""
        meeting = make_meeting_test_data()
        mars_group = Group.objects.get(acronym='mars')
        ames_group = Group.objects.get(acronym='ames')
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        assert SchedulingEvent.objects.filter(session=ames_stsa.session
            ).order_by('-id')[0].status_id == 'schedw'
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        SchedulingEvent.objects.create(session=mars_stsa.session, status=
            SessionStatusName.objects.get(slug='appr'), by=Person.objects.
            get(name='(System)'))
        url = reverse('ietf.secr.meetings.views.notifications', kwargs={
            'meeting_id': 72})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames, mars')
        now = datetime.datetime.now()
        then = datetime.datetime.now() + datetime.timedelta(hours=1)
        person = Person.objects.get(name='(System)')
        GroupEvent.objects.create(group=mars_group, time=now, type=
            'sent_notification', by=person, desc=
            'sent scheduled notification for %s' % meeting)
        ss = meeting.schedule.assignments.get(session__group=ames_group)
        ss.modified = then
        ss.save()
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames')
        mailbox_before = len(outbox)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(outbox), mailbox_before + 1)
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.
            session).order_by('-id')[0].status_id, 'sched')
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.
            session).order_by('-id')[0].status_id, 'sched')

    def test_meetings_rooms(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.rooms', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(len(q("#id_rooms_table tr input[type='checkbox']")
            ), meeting.room_set.count())
        SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.
            schedule, meeting.schedule.base, meeting.unofficial_schedule]
            ).delete()
        self.client.login(username='secretary', password='secretary+password')
        post_dict = {'room-TOTAL_FORMS': q('input[name="room-TOTAL_FORMS"]'
            ).val(), 'room-INITIAL_FORMS': q(
            'input[name="room-INITIAL_FORMS"]').val()}
        for i in range(meeting.room_set.count()):
            for attr in ['meeting', 'id', 'name', 'capacity', 'DELETE']:
                key = 'room-%d-%s' % (i, attr)
                post_dict[key] = q('input[name="%s"]' % key).val()
        response = self.client.post(url, post_dict)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(Room.objects.filter(meeting=meeting).count(), 0)
    <mask token>

    def test_meetings_times_delete(self):
        meeting = make_meeting_test_data()
        qs = TimeSlot.objects.filter(meeting=meeting, type='regular')
        before = qs.count()
        expected_deletion_count = qs.filter(time=qs.first().time).count()
        url = reverse('ietf.secr.meetings.views.times_delete', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name, 'time': qs.first().time.strftime('%Y:%m:%d:%H:%M')})
        redirect_url = reverse('ietf.secr.meetings.views.times', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        after = TimeSlot.objects.filter(meeting=meeting, type='regular').count(
            )
        self.assertEqual(after, before - expected_deletion_count)

    def test_meetings_times_edit(self):
        meeting = make_meeting_test_data()
        timeslot = TimeSlot.objects.filter(meeting=meeting, type='regular'
            ).first()
        url = reverse('ietf.secr.meetings.views.times_edit', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule', 'time':
            timeslot.time.strftime('%Y:%m:%d:%H:%M')})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '09:00', 'name': 'Testing'})
        self.assertEqual(response.status_code, 302)
        self.assertTrue(TimeSlot.objects.filter(meeting=meeting, name=
            'Testing'))

    def test_meetings_misc_sessions(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_meetings_misc_session_add_valid(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '02:00', 'name': 'Testing', 'short': 'test',
            'purpose_0': 'admin', 'purpose_1': 'reg', 'group': group.pk,
            'location': room.pk, 'remote_instructions':
            'http://webex.com/foobar'})
        self.assertRedirects(response, url)
        session = Session.objects.filter(meeting=meeting, name='Testing'
            ).first()
        self.assertTrue(session)
        self.assertEqual(session.timeslotassignments.first().timeslot.
            location, room)

    def test_meetings_misc_session_add_invalid(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '10', 'name': 'Testing', 'short': 'test', 'type':
            'reg', 'group': group.pk})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid format')

    def test_meetings_misc_session_edit(self):
        meeting = make_meeting_test_data()
        session = meeting.session_set.exclude(name='').first()
        timeslot = session.official_timeslotassignment().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_edit', kwargs=
            {'meeting_id': 72, 'schedule_name': meeting.schedule.name,
            'slot_id': timeslot.pk})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': 72, 'schedule_name': 'test-schedule'})
        new_time = timeslot.time + datetime.timedelta(days=1)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'name': 'IETF Hackathon', 'short':
            'hackathon', 'location': timeslot.location.id, 'group': session
            .group.id, 'time': new_time.strftime('%H:%M'), 'duration':
            '01:00', 'day': '2', 'purpose_0': 'coding', 'purpose_1':
            'other', 'remote_instructions': 'http://webex.com/foobar'})
        self.assertRedirects(response, redirect_url)
        timeslot = session.official_timeslotassignment().timeslot
        self.assertEqual(timeslot.time, new_time)

    def test_meetings_misc_session_delete(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_delete',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': meeting.number, 'schedule_name': schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, target)
        self.assertFalse(schedule.assignments.filter(timeslot=slot))

    def test_meetings_misc_session_cancel(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_cancel',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        session = slot.sessionassignments.filter(schedule=schedule).first(
            ).session
        self.assertEqual(SchedulingEvent.objects.filter(session=session).
            order_by('-id')[0].status_id, 'canceled')

    def test_meetings_regular_session_edit(self):
        meeting = make_meeting_test_data()
        session = Session.objects.filter(meeting=meeting, group__acronym='mars'
            ).first()
        url = reverse('ietf.secr.meetings.views.regular_session_edit',
            kwargs={'meeting_id': meeting.number, 'schedule_name': meeting.
            schedule.name, 'session_id': session.id})
        redirect_url = reverse('ietf.secr.meetings.views.regular_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': meeting.
            schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'agenda_note': 'TEST'})
        self.assertRedirects(response, redirect_url)
        session = Session.objects.get(id=session.id)
        self.assertEqual(session.agenda_note, 'TEST')

    def test_get_times(self):
        meeting = make_meeting_test_data()
        timeslot = meeting.timeslot_set.filter(type='regular').first()
        day = (timeslot.time.weekday() + 1) % 7 + 1
        times = get_times(meeting, day)
        values = [x[0] for x in times]
        self.assertTrue(times)
        self.assertTrue(timeslot.time.strftime('%H%M') in values)

<mask token>


class SecrMeetingTestCase(TestCase):
    <mask token>

    def setUp(self):
        super().setUp()
        self.bluesheet_dir = self.tempdir('bluesheet')
        self.bluesheet_path = os.path.join(self.bluesheet_dir, 'blue_sheet.rtf'
            )
        self.saved_secr_blue_sheet_path = settings.SECR_BLUE_SHEET_PATH
        settings.SECR_BLUE_SHEET_PATH = self.bluesheet_path
        self.saved_secr_proceedings_dir = settings.SECR_PROCEEDINGS_DIR
        settings.SECR_PROCEEDINGS_DIR = settings.AGENDA_PATH

    def tearDown(self):
        settings.SECR_PROCEEDINGS_DIR = self.saved_secr_proceedings_dir
        settings.SECR_BLUE_SHEET_PATH = self.saved_secr_blue_sheet_path
        shutil.rmtree(self.bluesheet_dir)
        super().tearDown()

    def test_main(self):
        """Main Test"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.main')
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'meeting': meeting.number})
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':
            meeting.number})
        self.assertRedirects(response, url)

    def test_view(self):
        """View Test"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':
            meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual({option.get('value') for option in q(
            '#id_schedule_selector option:not([value=""])')}, {'base',
            'test-schedule', 'test-unofficial-schedule'})

    def test_add_meeting(self):
        """Add Meeting"""
        meeting = make_meeting_test_data()
        number = int(meeting.number) + 1
        count = Meeting.objects.count()
        url = reverse('ietf.secr.meetings.views.add')
        post_data = dict(number=number, city='Toronto', date='2014-07-20',
            country='CA', time_zone='America/New_York', venue_name='Hilton',
            days=6, venue_addr='100 First Ave',
            idsubmit_cutoff_day_offset_00=13, idsubmit_cutoff_day_offset_01
            =20, idsubmit_cutoff_time_utc=datetime.timedelta(hours=23,
            minutes=59, seconds=59), idsubmit_cutoff_warning_days=datetime.
            timedelta(days=21), submission_start_day_offset=90,
            submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, group_conflict_types=(
            'conflict', 'conflic2', 'key_participant'))
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Meeting.objects.count(), count + 1)
        new_meeting = Meeting.objects.get(number=number)
        self.assertTrue(new_meeting.schedule)
        self.assertEqual(new_meeting.schedule.name, 'sec-retary-1')
        self.assertTrue(new_meeting.schedule.base)
        self.assertEqual(new_meeting.schedule.base.name, 'base')
        self.assertEqual(new_meeting.attendees, None)
        self.assertCountEqual([cn.slug for cn in new_meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])

    def test_add_meeting_default_conflict_types(self):
        """Add meeting should default to same conflict types as previous meeting"""

        def _run_test(mtg):
            url = reverse('ietf.secr.meetings.views.add')
            r = self.client.get(url)
            q = PyQuery(r.content)
            selected_items = q('#id_group_conflict_types input[checked]')
            selected_values = [si.value for si in selected_items]
            expected_values = [cn.slug for cn in mtg.group_conflict_types.all()
                ]
            self.assertCountEqual(selected_values, expected_values)
        self.client.login(username='secretary', password='secretary+password')
        meeting = MeetingFactory(type_id='ietf', group_conflicts=[])
        _run_test(meeting)
        meeting.group_conflict_types.add(ConstraintName.objects.filter(
            is_group_conflict=True).first())
        self.assertEqual(meeting.group_conflict_types.count(), 1)
        _run_test(meeting)
        meeting.group_conflict_types.clear()
        for cn in ConstraintName.objects.filter(is_group_conflict=True)[::2]:
            meeting.group_conflict_types.add(cn)
        self.assertGreater(meeting.group_conflict_types.count(), 1)
        _run_test(meeting)

    def test_edit_meeting(self):
        """Edit Meeting"""
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.edit_meeting', kwargs={
            'meeting_id': meeting.number})
        post_data = dict(number=meeting.number, date='2014-07-20', city=
            'Toronto', days=7, idsubmit_cutoff_day_offset_00=13,
            idsubmit_cutoff_day_offset_01=20, idsubmit_cutoff_time_utc=
            datetime.timedelta(hours=23, minutes=59, seconds=59),
            idsubmit_cutoff_warning_days=datetime.timedelta(days=21),
            submission_start_day_offset=90, submission_cutoff_day_offset=26,
            submission_correction_day_offset=50, attendees=1234,
            group_conflict_types=[cn.slug for cn in ConstraintName.objects.
            filter(is_group_conflict=True).exclude(meeting=meeting)])
        self.assertGreater(len(post_data['group_conflict_types']), 0)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        meeting = Meeting.objects.get(number=meeting.number)
        self.assertEqual(meeting.city, 'Toronto')
        self.assertEqual(meeting.attendees, 1234)
        self.assertCountEqual([cn.slug for cn in meeting.
            group_conflict_types.all()], post_data['group_conflict_types'])

    def test_blue_sheets_upload(self):
        """Test Bluesheets"""
        meeting = make_meeting_test_data()
        (Path(settings.SECR_PROCEEDINGS_DIR) / str(meeting.number) /
            'bluesheets').mkdir(parents=True)
        url = reverse('ietf.secr.meetings.views.blue_sheet', kwargs={
            'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        group = Group.objects.filter(type='wg', state='active').first()
        file = StringIO('dummy bluesheet')
        file.name = 'bluesheets-%s-%s.pdf' % (meeting.number, group.acronym)
        files = {'file': file}
        response = self.client.post(url, files)
        self.assertEqual(response.status_code, 302)
        path = os.path.join(settings.SECR_PROCEEDINGS_DIR, str(meeting.
            number), 'bluesheets')
        self.assertEqual(len(os.listdir(path)), 1)

    def test_blue_sheets_generate(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.blue_sheet_generate',
            kwargs={'meeting_id': meeting.number})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(os.path.exists(self.bluesheet_path))

    def test_notifications(self):
        """Test Notifications"""
        meeting = make_meeting_test_data()
        mars_group = Group.objects.get(acronym='mars')
        ames_group = Group.objects.get(acronym='ames')
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        assert SchedulingEvent.objects.filter(session=ames_stsa.session
            ).order_by('-id')[0].status_id == 'schedw'
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        SchedulingEvent.objects.create(session=mars_stsa.session, status=
            SessionStatusName.objects.get(slug='appr'), by=Person.objects.
            get(name='(System)'))
        url = reverse('ietf.secr.meetings.views.notifications', kwargs={
            'meeting_id': 72})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames, mars')
        now = datetime.datetime.now()
        then = datetime.datetime.now() + datetime.timedelta(hours=1)
        person = Person.objects.get(name='(System)')
        GroupEvent.objects.create(group=mars_group, time=now, type=
            'sent_notification', by=person, desc=
            'sent scheduled notification for %s' % meeting)
        ss = meeting.schedule.assignments.get(session__group=ames_group)
        ss.modified = then
        ss.save()
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(), 'ames')
        mailbox_before = len(outbox)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(outbox), mailbox_before + 1)
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.
            session).order_by('-id')[0].status_id, 'sched')
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.
            session).order_by('-id')[0].status_id, 'sched')

    def test_meetings_rooms(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.rooms', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(len(q("#id_rooms_table tr input[type='checkbox']")
            ), meeting.room_set.count())
        SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.
            schedule, meeting.schedule.base, meeting.unofficial_schedule]
            ).delete()
        self.client.login(username='secretary', password='secretary+password')
        post_dict = {'room-TOTAL_FORMS': q('input[name="room-TOTAL_FORMS"]'
            ).val(), 'room-INITIAL_FORMS': q(
            'input[name="room-INITIAL_FORMS"]').val()}
        for i in range(meeting.room_set.count()):
            for attr in ['meeting', 'id', 'name', 'capacity', 'DELETE']:
                key = 'room-%d-%s' % (i, attr)
                post_dict[key] = q('input[name="%s"]' % key).val()
        response = self.client.post(url, post_dict)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(Room.objects.filter(meeting=meeting).count(), 0)
    <mask token>

    def test_meetings_times_delete(self):
        meeting = make_meeting_test_data()
        qs = TimeSlot.objects.filter(meeting=meeting, type='regular')
        before = qs.count()
        expected_deletion_count = qs.filter(time=qs.first().time).count()
        url = reverse('ietf.secr.meetings.views.times_delete', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name, 'time': qs.first().time.strftime('%Y:%m:%d:%H:%M')})
        redirect_url = reverse('ietf.secr.meetings.views.times', kwargs={
            'meeting_id': meeting.number, 'schedule_name': meeting.schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        after = TimeSlot.objects.filter(meeting=meeting, type='regular').count(
            )
        self.assertEqual(after, before - expected_deletion_count)

    def test_meetings_times_edit(self):
        meeting = make_meeting_test_data()
        timeslot = TimeSlot.objects.filter(meeting=meeting, type='regular'
            ).first()
        url = reverse('ietf.secr.meetings.views.times_edit', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule', 'time':
            timeslot.time.strftime('%Y:%m:%d:%H:%M')})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '09:00', 'name': 'Testing'})
        self.assertEqual(response.status_code, 302)
        self.assertTrue(TimeSlot.objects.filter(meeting=meeting, name=
            'Testing'))

    def test_meetings_misc_sessions(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_meetings_misc_session_add_valid(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '02:00', 'name': 'Testing', 'short': 'test',
            'purpose_0': 'admin', 'purpose_1': 'reg', 'group': group.pk,
            'location': room.pk, 'remote_instructions':
            'http://webex.com/foobar'})
        self.assertRedirects(response, url)
        session = Session.objects.filter(meeting=meeting, name='Testing'
            ).first()
        self.assertTrue(session)
        self.assertEqual(session.timeslotassignments.first().timeslot.
            location, room)

    def test_meetings_misc_session_add_invalid(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': 72, 'schedule_name': 'test-schedule'})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, {'day': '1', 'time': '08:00',
            'duration': '10', 'name': 'Testing', 'short': 'test', 'type':
            'reg', 'group': group.pk})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid format')

    def test_meetings_misc_session_edit(self):
        meeting = make_meeting_test_data()
        session = meeting.session_set.exclude(name='').first()
        timeslot = session.official_timeslotassignment().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_edit', kwargs=
            {'meeting_id': 72, 'schedule_name': meeting.schedule.name,
            'slot_id': timeslot.pk})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': 72, 'schedule_name': 'test-schedule'})
        new_time = timeslot.time + datetime.timedelta(days=1)
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'name': 'IETF Hackathon', 'short':
            'hackathon', 'location': timeslot.location.id, 'group': session
            .group.id, 'time': new_time.strftime('%H:%M'), 'duration':
            '01:00', 'day': '2', 'purpose_0': 'coding', 'purpose_1':
            'other', 'remote_instructions': 'http://webex.com/foobar'})
        self.assertRedirects(response, redirect_url)
        timeslot = session.official_timeslotassignment().timeslot
        self.assertEqual(timeslot.time, new_time)

    def test_meetings_misc_session_delete(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_delete',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={
            'meeting_id': meeting.number, 'schedule_name': schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, target)
        self.assertFalse(schedule.assignments.filter(timeslot=slot))

    def test_meetings_misc_session_cancel(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first(
            ).timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_cancel',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name, 'slot_id': slot.id})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': schedule
            .name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post': 'yes'})
        self.assertRedirects(response, redirect_url)
        session = slot.sessionassignments.filter(schedule=schedule).first(
            ).session
        self.assertEqual(SchedulingEvent.objects.filter(session=session).
            order_by('-id')[0].status_id, 'canceled')

    def test_meetings_regular_session_edit(self):
        meeting = make_meeting_test_data()
        session = Session.objects.filter(meeting=meeting, group__acronym='mars'
            ).first()
        url = reverse('ietf.secr.meetings.views.regular_session_edit',
            kwargs={'meeting_id': meeting.number, 'schedule_name': meeting.
            schedule.name, 'session_id': session.id})
        redirect_url = reverse('ietf.secr.meetings.views.regular_sessions',
            kwargs={'meeting_id': meeting.number, 'schedule_name': meeting.
            schedule.name})
        self.client.login(username='secretary', password='secretary+password')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'agenda_note': 'TEST'})
        self.assertRedirects(response, redirect_url)
        session = Session.objects.get(id=session.id)
        self.assertEqual(session.agenda_note, 'TEST')

    def test_get_times(self):
        meeting = make_meeting_test_data()
        timeslot = meeting.timeslot_set.filter(type='regular').first()
        day = (timeslot.time.weekday() + 1) % 7 + 1
        times = get_times(meeting, day)
        values = [x[0] for x in times]
        self.assertTrue(times)
        self.assertTrue(timeslot.time.strftime('%H%M') in values)

# Copyright The IETF Trust 2013-2020, All Rights Reserved
# -*- coding: utf-8 -*-


import datetime
import os
import shutil

from pathlib import Path
from pyquery import PyQuery
from io import StringIO

import debug         # pyflakes:ignore

from django.conf import settings
from django.urls import reverse

from ietf.group.models import Group, GroupEvent
from ietf.meeting.factories import MeetingFactory
from ietf.meeting.models import Meeting, Room, TimeSlot, SchedTimeSessAssignment, Session, SchedulingEvent
from ietf.meeting.test_data import make_meeting_test_data
from ietf.name.models import ConstraintName, SessionStatusName
from ietf.person.models import Person
from ietf.secr.meetings.forms import get_times
from ietf.utils.mail import outbox
from ietf.utils.test_utils import TestCase 


class SecrMeetingTestCase(TestCase):
    settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['AGENDA_PATH']
    def setUp(self):
        super().setUp()
        self.bluesheet_dir = self.tempdir('bluesheet')
        self.bluesheet_path = os.path.join(self.bluesheet_dir,'blue_sheet.rtf')
        self.saved_secr_blue_sheet_path = settings.SECR_BLUE_SHEET_PATH
        settings.SECR_BLUE_SHEET_PATH = self.bluesheet_path

        # n.b., the bluesheet upload relies on SECR_PROCEEDINGS_DIR being the same
        # as AGENDA_PATH. This is probably a bug, but may not be worth fixing if
        # the secr app is on the way out.
        self.saved_secr_proceedings_dir = settings.SECR_PROCEEDINGS_DIR
        settings.SECR_PROCEEDINGS_DIR = settings.AGENDA_PATH

    def tearDown(self):
        settings.SECR_PROCEEDINGS_DIR = self.saved_secr_proceedings_dir
        settings.SECR_BLUE_SHEET_PATH = self.saved_secr_blue_sheet_path
        shutil.rmtree(self.bluesheet_dir)
        super().tearDown()

    def test_main(self):
        "Main Test"
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.main')
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'meeting':meeting.number})
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':meeting.number})
        self.assertRedirects(response,url)

    def test_view(self):
        "View Test"
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.view', kwargs={'meeting_id':meeting.number})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual({option.get('value') for option in q('#id_schedule_selector option:not([value=""])')}, {'base', 'test-schedule', 'test-unofficial-schedule'})
         
    def test_add_meeting(self):
        "Add Meeting"
        meeting = make_meeting_test_data()
        number = int(meeting.number) + 1
        count = Meeting.objects.count()
        url = reverse('ietf.secr.meetings.views.add')
        post_data = dict(
            number=number,city='Toronto',date='2014-07-20',country='CA',
            time_zone='America/New_York',venue_name='Hilton',
            days=6,
            venue_addr='100 First Ave',
            idsubmit_cutoff_day_offset_00=13,
            idsubmit_cutoff_day_offset_01=20,
            idsubmit_cutoff_time_utc     =datetime.timedelta(hours=23, minutes=59, seconds=59),
            idsubmit_cutoff_warning_days =datetime.timedelta(days=21),
            submission_start_day_offset=90,
            submission_cutoff_day_offset=26,
            submission_correction_day_offset=50,
            group_conflict_types=('conflict', 'conflic2', 'key_participant'),
        )

        self.client.login(username='secretary', password='secretary+password')
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(Meeting.objects.count(),count + 1)
        new_meeting = Meeting.objects.get(number=number)
        
        self.assertTrue(new_meeting.schedule)
        self.assertEqual(new_meeting.schedule.name, 'sec-retary-1')
        self.assertTrue(new_meeting.schedule.base)
        self.assertEqual(new_meeting.schedule.base.name, 'base')
        self.assertEqual(new_meeting.attendees, None)
        self.assertCountEqual(
            [cn.slug for cn in new_meeting.group_conflict_types.all()],
            post_data['group_conflict_types'],
        )

    def test_add_meeting_default_conflict_types(self):
        """Add meeting should default to same conflict types as previous meeting"""
        def _run_test(mtg):
            url = reverse('ietf.secr.meetings.views.add')
            r = self.client.get(url)
            q = PyQuery(r.content)
            selected_items = q('#id_group_conflict_types input[checked]')
            selected_values = [si.value for si in selected_items]
            expected_values = [cn.slug for cn in mtg.group_conflict_types.all()]
            self.assertCountEqual(selected_values, expected_values)

        self.client.login(username='secretary', password='secretary+password')

        meeting = MeetingFactory(type_id='ietf', group_conflicts=[])  # start with no conflicts selected
        _run_test(meeting)

        # enable one
        meeting.group_conflict_types.add(ConstraintName.objects.filter(is_group_conflict=True).first())
        self.assertEqual(meeting.group_conflict_types.count(), 1)
        _run_test(meeting)

        # enable a few ([::2] selects every other)
        meeting.group_conflict_types.clear()
        for cn in ConstraintName.objects.filter(is_group_conflict=True)[::2]:
            meeting.group_conflict_types.add(cn)
        self.assertGreater(meeting.group_conflict_types.count(), 1)
        _run_test(meeting)

    def test_edit_meeting(self):
        "Edit Meeting"
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.edit_meeting',kwargs={'meeting_id':meeting.number})
        post_data = dict(number=meeting.number,date='2014-07-20',city='Toronto',
                         days=7,
                         idsubmit_cutoff_day_offset_00=13,
                         idsubmit_cutoff_day_offset_01=20,
                         idsubmit_cutoff_time_utc     =datetime.timedelta(hours=23, minutes=59, seconds=59),
                         idsubmit_cutoff_warning_days =datetime.timedelta(days=21),
                         submission_start_day_offset=90,
                         submission_cutoff_day_offset=26,
                         submission_correction_day_offset=50,
                         attendees=1234,
                         group_conflict_types=[
                             cn.slug for cn in ConstraintName.objects.filter(
                                 is_group_conflict=True
                             ).exclude(
                                 meeting=meeting,  # replace original set with those not assigned to the meeting
                             )
                         ]
                    )
        self.assertGreater(len(post_data['group_conflict_types']), 0)  # test should include at least one
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, post_data, follow=True)
        self.assertEqual(response.status_code, 200)
        meeting = Meeting.objects.get(number=meeting.number)
        self.assertEqual(meeting.city,'Toronto')
        self.assertEqual(meeting.attendees,1234)
        self.assertCountEqual(
            [cn.slug for cn in meeting.group_conflict_types.all()],
            post_data['group_conflict_types'],
        )

    def test_blue_sheets_upload(self):
        "Test Bluesheets"
        meeting = make_meeting_test_data()
        (Path(settings.SECR_PROCEEDINGS_DIR) / str(meeting.number) / 'bluesheets').mkdir(parents=True)

        url = reverse('ietf.secr.meetings.views.blue_sheet',kwargs={'meeting_id':meeting.number})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        
        # test upload
        group = Group.objects.filter(type='wg',state='active').first()
        file = StringIO('dummy bluesheet')
        file.name = "bluesheets-%s-%s.pdf" % (meeting.number,group.acronym)
        files = {'file':file}
        response = self.client.post(url, files)
        self.assertEqual(response.status_code, 302)
        path = os.path.join(settings.SECR_PROCEEDINGS_DIR,str(meeting.number),'bluesheets')
        self.assertEqual(len(os.listdir(path)),1)

    def test_blue_sheets_generate(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.blue_sheet_generate',kwargs={'meeting_id':meeting.number})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertTrue(os.path.exists(self.bluesheet_path))
        
    def test_notifications(self):
        "Test Notifications"
        meeting = make_meeting_test_data()
        mars_group = Group.objects.get(acronym='mars')
        ames_group = Group.objects.get(acronym='ames')
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        assert SchedulingEvent.objects.filter(session=ames_stsa.session).order_by('-id')[0].status_id == 'schedw'
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        SchedulingEvent.objects.create(session=mars_stsa.session, status=SessionStatusName.objects.get(slug='appr'), by=Person.objects.get(name="(System)"))
        url = reverse('ietf.secr.meetings.views.notifications',kwargs={'meeting_id':72})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(),'ames, mars')

        # test that only changes since last notification show up
        now = datetime.datetime.now()
        then = datetime.datetime.now()+datetime.timedelta(hours=1)
        person = Person.objects.get(name="(System)")
        GroupEvent.objects.create(group=mars_group,time=now,type='sent_notification',
                                  by=person,desc='sent scheduled notification for %s' % meeting)
        ss = meeting.schedule.assignments.get(session__group=ames_group)
        ss.modified = then
        ss.save()
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(q('#id_notification_list').html(),'ames')
        
        # test post: email goes out, status changed
        mailbox_before = len(outbox)
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(outbox), mailbox_before + 1)
        ames_stsa = meeting.schedule.assignments.get(session__group=ames_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=ames_stsa.session).order_by('-id')[0].status_id, 'sched')
        mars_stsa = meeting.schedule.assignments.get(session__group=mars_group)
        self.assertEqual(SchedulingEvent.objects.filter(session=mars_stsa.session).order_by('-id')[0].status_id, 'sched')

    def test_meetings_rooms(self):
        meeting = make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.rooms',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        q = PyQuery(response.content)
        self.assertEqual(len(q("#id_rooms_table tr input[type='checkbox']")),meeting.room_set.count())
        
        # test delete
        # first unschedule sessions so we can delete
        SchedTimeSessAssignment.objects.filter(schedule__in=[meeting.schedule, meeting.schedule.base, meeting.unofficial_schedule]).delete()
        self.client.login(username="secretary", password="secretary+password")
        post_dict = {
            'room-TOTAL_FORMS':  q('input[name="room-TOTAL_FORMS"]').val(),
            'room-INITIAL_FORMS': q('input[name="room-INITIAL_FORMS"]').val(),
        }
        for i in range(meeting.room_set.count()):
            for attr in ['meeting','id','name','capacity','DELETE']:
                key = 'room-%d-%s' % (i,attr)
                post_dict[key] = q('input[name="%s"]' % key).val()
        response = self.client.post(url, post_dict)
        self.assertEqual(response.status_code, 302)
        self.assertEqual(Room.objects.filter(meeting=meeting).count(),0)
        
    def test_meetings_times(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.times',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {
            'day': 0,
            'time':'08:00',
            'duration':'01:00',
            'name':'Test Morning Session'
        }, follow=True)
        self.assertRedirects(response, url)
        self.assertContains(response, 'Test Morning Session')

    def test_meetings_times_delete(self):
        meeting = make_meeting_test_data()
        qs = TimeSlot.objects.filter(meeting=meeting,type='regular')
        before = qs.count()
        expected_deletion_count = qs.filter(time=qs.first().time).count() 
        url = reverse('ietf.secr.meetings.views.times_delete',kwargs={
            'meeting_id':meeting.number,
            'schedule_name':meeting.schedule.name,
            'time':qs.first().time.strftime("%Y:%m:%d:%H:%M")
        })
        redirect_url = reverse('ietf.secr.meetings.views.times',kwargs={
            'meeting_id':meeting.number,
            'schedule_name':meeting.schedule.name
        })
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post':'yes'})
        self.assertRedirects(response, redirect_url)
        after = TimeSlot.objects.filter(meeting=meeting,type='regular').count()
        self.assertEqual(after,before - expected_deletion_count)
        
    def test_meetings_times_edit(self):
        meeting = make_meeting_test_data()
        timeslot = TimeSlot.objects.filter(meeting=meeting,type='regular').first()
        url = reverse('ietf.secr.meetings.views.times_edit',kwargs={
            'meeting_id':72,
            'schedule_name':'test-schedule',
            'time':timeslot.time.strftime("%Y:%m:%d:%H:%M")
        })
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, {
            'day':'1',
            'time':'08:00',
            'duration':'09:00',
            'name':'Testing'
        })
        self.assertEqual(response.status_code, 302)
        self.assertTrue(TimeSlot.objects.filter(meeting=meeting,name='Testing'))
        
    def test_meetings_misc_sessions(self):
        make_meeting_test_data()
        url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
    
    def test_meetings_misc_session_add_valid(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, {
            'day':'1',
            'time':'08:00',
            'duration':'02:00',
            'name':'Testing',
            'short':'test',
            'purpose_0': 'admin',  # purpose
            'purpose_1':'reg',  # type
            'group':group.pk,
            'location': room.pk,
            'remote_instructions': 'http://webex.com/foobar',
        })
        self.assertRedirects(response, url)
        session = Session.objects.filter(meeting=meeting, name='Testing').first()
        self.assertTrue(session)

        self.assertEqual(session.timeslotassignments.first().timeslot.location, room)

    def test_meetings_misc_session_add_invalid(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym='secretariat')
        url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.post(url, {
            'day':'1',
            'time':'08:00',
            'duration':'10',
            'name':'Testing',
            'short':'test',
            'type':'reg',
            'group':group.pk,
        })
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid format')

    def test_meetings_misc_session_edit(self):
        meeting = make_meeting_test_data()
        session = meeting.session_set.exclude(name='').first()   # get first misc session
        timeslot = session.official_timeslotassignment().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_edit',kwargs={'meeting_id':72,'schedule_name':meeting.schedule.name,'slot_id':timeslot.pk})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions',kwargs={'meeting_id':72,'schedule_name':'test-schedule'})
        new_time = timeslot.time + datetime.timedelta(days=1)
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {
            'name':'IETF Hackathon',
            'short':'hackathon',
            'location':timeslot.location.id,
            'group':session.group.id,
            'time':new_time.strftime('%H:%M'),
            'duration':'01:00',
            'day':'2',
            'purpose_0': 'coding',  # purpose
            'purpose_1': 'other',  # type
            'remote_instructions': 'http://webex.com/foobar',
        })
        self.assertRedirects(response, redirect_url)
        timeslot = session.official_timeslotassignment().timeslot
        self.assertEqual(timeslot.time,new_time)

    def test_meetings_misc_session_delete(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_delete', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name,'slot_id':slot.id})
        target = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post':'yes'})
        self.assertRedirects(response, target)
        self.assertFalse(schedule.assignments.filter(timeslot=slot))

    def test_meetings_misc_session_cancel(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule.base
        slot = schedule.assignments.filter(timeslot__type='reg').first().timeslot
        url = reverse('ietf.secr.meetings.views.misc_session_cancel', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name,'slot_id':slot.id})
        redirect_url = reverse('ietf.secr.meetings.views.misc_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':schedule.name})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'post':'yes'})
        self.assertRedirects(response, redirect_url)
        session = slot.sessionassignments.filter(schedule=schedule).first().session
        self.assertEqual(SchedulingEvent.objects.filter(session=session).order_by('-id')[0].status_id, 'canceled')

    def test_meetings_regular_session_edit(self):
        meeting = make_meeting_test_data()
        session = Session.objects.filter(meeting=meeting,group__acronym='mars').first()
        url = reverse('ietf.secr.meetings.views.regular_session_edit', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name,'session_id':session.id})
        redirect_url = reverse('ietf.secr.meetings.views.regular_sessions', kwargs={'meeting_id':meeting.number,'schedule_name':meeting.schedule.name})
        self.client.login(username="secretary", password="secretary+password")
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        response = self.client.post(url, {'agenda_note':'TEST'})
        self.assertRedirects(response, redirect_url)
        session = Session.objects.get(id=session.id)
        self.assertEqual(session.agenda_note, 'TEST')

    # ----------------------
    # Unit Tests
    # -----------------------
    def test_get_times(self):
        meeting = make_meeting_test_data()
        timeslot = meeting.timeslot_set.filter(type='regular').first()
        day = (timeslot.time.weekday() + 1) % 7 + 1  # fix up to match django __week_day filter
        times = get_times(meeting,day)
        values = [ x[0] for x in times ]
        self.assertTrue(times)
        self.assertTrue(timeslot.time.strftime('%H%M') in values)
