# Copyright The IETF Trust 2013-2020, All Rights Reserved # -*- coding: utf-8 -*- import datetime from urllib.parse import urlsplit from django.urls import reverse as urlreverse import debug # pyflakes:ignore from ietf.name.models import TimerangeName from ietf.group.models import Group from ietf.meeting.models import Schedule, TimeSlot, Session, SchedTimeSessAssignment, Meeting, Constraint from ietf.meeting.test_data import make_meeting_test_data from ietf.person.models import Person from ietf.utils.test_utils import TestCase from ietf.utils.mail import outbox class ApiTests(TestCase): def test_update_schedule(self): meeting = make_meeting_test_data() schedule = Schedule.objects.get(meeting__number=72,name="test-schedule") mars_session = Session.objects.filter(meeting=meeting, group__acronym="mars").first() ames_session = Session.objects.filter(meeting=meeting, group__acronym="ames").first() mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session,schedule__name='test-schedule') mars_slot = mars_scheduled.timeslot ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule') ames_slot = ames_scheduled.timeslot def do_unschedule(assignment): url = urlreverse("ietf.meeting.ajax.assignment_json", kwargs=dict(num=assignment.session.meeting.number, owner=assignment.schedule.owner_email(), name=assignment.schedule.name, assignment_id=assignment.pk,)) return self.client.delete(url) def do_schedule(schedule,session,timeslot): url = urlreverse("ietf.meeting.ajax.assignments_json", kwargs=dict(num=session.meeting.number, owner=schedule.owner_email(), name=schedule.name,)) post_data = '{ "session_id": "%s", "timeslot_id": "%s" }'%(session.pk,timeslot.pk) return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded') def do_extend(schedule, assignment): session = assignment.session url = urlreverse("ietf.meeting.ajax.assignments_json", kwargs=dict(num=session.meeting.number, owner=schedule.owner_email(), name=schedule.name,)) post_data = '{ "session_id": "%s", "timeslot_id": "%s", "extendedfrom_id": "%s" }'%(session.pk,assignment.timeslot.slot_to_the_right.pk,assignment.pk) return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded') # not logged in # faulty delete r = do_unschedule(mars_scheduled) self.assertEqual(r.status_code, 403) self.assertEqual(SchedTimeSessAssignment.objects.get(pk=mars_scheduled.pk).session, mars_session) # faulty post r = do_schedule(schedule,ames_session,mars_slot) self.assertEqual(r.status_code, 403) # logged in as non-owner # faulty delete self.client.login(username="ad", password="ad+password") r = do_unschedule(mars_scheduled) self.assertEqual(r.status_code, 403) self.assertTrue("error" in r.json()) # faulty post r = do_schedule(schedule,ames_session,mars_slot) self.assertEqual(r.status_code, 403) # Put ames in the same timeslot as mars self.client.login(username="plain", password='plain+password') r = do_unschedule(ames_scheduled) self.assertEqual(r.status_code, 200) self.assertNotIn("error", r.json()) r = do_schedule(schedule,ames_session,mars_slot) self.assertEqual(r.status_code, 201) # Move the two timeslots close enough together for extension to work ames_slot_qs=TimeSlot.objects.filter(id=ames_slot.id) ames_slot_qs.update(time=mars_slot.time+mars_slot.duration+datetime.timedelta(minutes=10)) # Extend the mars session r = do_extend(schedule,mars_scheduled) self.assertEqual(r.status_code, 201) self.assertTrue("error" not in r.json()) self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),2) # Unschedule mars r = do_unschedule(mars_scheduled) self.assertEqual(r.status_code, 200) self.assertNotIn("error", r.json()) # Make sure it got both the original and extended session self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),0) self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule').timeslot, mars_slot) def test_constraints_json(self): meeting = make_meeting_test_data() session = Session.objects.filter(meeting=meeting, group__acronym="mars").select_related("group").first() c_ames = Constraint.objects.create(meeting=meeting, source=session.group, target=Group.objects.get(acronym="ames"), name_id="conflict") c_person = Constraint.objects.create(meeting=meeting, source=session.group, person=Person.objects.get(user__username="ad"), name_id="bethere") c_adjacent = Constraint.objects.create(meeting=meeting, source=session.group, target=Group.objects.get(acronym="irg"), name_id="wg_adjacent") c_time_relation = Constraint.objects.create(meeting=meeting, source=session.group, time_relation='subsequent-days', name_id="time_relation") c_timerange = Constraint.objects.create(meeting=meeting, source=session.group, name_id="timerange") c_timerange.timeranges.set(TimerangeName.objects.filter(slug__startswith='monday')) r = self.client.get(urlreverse("ietf.meeting.ajax.session_constraints", kwargs=dict(num=meeting.number, sessionid=session.pk))) self.assertEqual(r.status_code, 200) constraints = r.json() expected_keys = set([c_ames.pk, c_person.pk, c_adjacent.pk, c_time_relation.pk, c_timerange.pk]) self.assertEqual(expected_keys, set(c["constraint_id"] for c in constraints)) def test_meeting_json(self): meeting = make_meeting_test_data() r = self.client.get(urlreverse("ietf.meeting.ajax.meeting_json", kwargs=dict(num=meeting.number))) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info["name"], meeting.number) def test_get_room_json(self): meeting = make_meeting_test_data() room = meeting.room_set.first() r = self.client.get(urlreverse("ietf.meeting.ajax.timeslot_roomurl", kwargs=dict(num=meeting.number, roomid=room.pk))) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info["name"], room.name) def test_create_new_room(self): meeting = make_meeting_test_data() timeslots_before = meeting.timeslot_set.filter(type='regular').count() url = urlreverse("ietf.meeting.ajax.timeslot_roomsurl", kwargs=dict(num=meeting.number)) post_data = { "name": "new room", "capacity": "50" , "resources": [], "session_types":['regular']} # unauthorized post r = self.client.post(url, post_data) self.assertEqual(r.status_code, 302) self.assertTrue(not meeting.room_set.filter(name="new room")) # create room self.client.login(username="secretary", password="secretary+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 302) self.assertTrue(meeting.room_set.filter(name="new room")) timeslots_after = meeting.timeslot_set.filter(type='regular').count() # It's not clear that what that ajax function is doing is the right thing to do, # but it currently makes a new timeslot for any existing timeslot. # The condition tested below relies on the timeslots before this test all having different start and end times self.assertEqual( timeslots_after, 2 * timeslots_before) def test_delete_room(self): meeting = make_meeting_test_data() room = meeting.room_set.first() timeslots_before = list(room.timeslot_set.values_list("pk", flat=True)) url = urlreverse("ietf.meeting.ajax.timeslot_roomurl", kwargs=dict(num=meeting.number, roomid=room.pk)) # unauthorized delete r = self.client.delete(url) self.assertEqual(r.status_code, 302) self.assertTrue(meeting.room_set.filter(pk=room.pk)) # delete self.client.login(username="secretary", password="secretary+password") r = self.client.delete(url) self.assertTrue(not meeting.room_set.filter(pk=room.pk)) self.assertTrue(not TimeSlot.objects.filter(pk__in=timeslots_before)) # This really belongs in group tests def test_group_json(self): make_meeting_test_data() group = Group.objects.get(acronym="mars") url = urlreverse("ietf.group.views.group_json", kwargs=dict(acronym=group.acronym)) r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info["name"], group.name) # This really belongs in person tests def test_person_json(self): make_meeting_test_data() person = Person.objects.get(user__username="ad") url = urlreverse("ietf.person.ajax.person_json", kwargs=dict(personid=person.pk)) r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info["name"], person.name) def test_sessions_json(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.ajax.sessions_json",kwargs=dict(num=meeting.number)) r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(set([x['short_name'] for x in info]),set([s.session.short_name for s in meeting.schedule.assignments.filter(session__type_id='regular')])) schedule = meeting.schedule url = urlreverse("ietf.meeting.ajax.assignments_json", kwargs=dict(num=meeting.number,owner=schedule.owner_email(),name=schedule.name)) r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(len(info),schedule.assignments.count()) def test_slot_json(self): meeting = make_meeting_test_data() slot = meeting.timeslot_set.all()[0] url = urlreverse("ietf.meeting.ajax.timeslot_sloturl", kwargs=dict(num=meeting.number, slotid=slot.pk)) r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info["timeslot_id"], slot.pk) def test_create_new_slot(self): meeting = make_meeting_test_data() slot_time = datetime.date.today() url = urlreverse("ietf.meeting.ajax.timeslot_slotsurl", kwargs=dict(num=meeting.number)) post_data = { 'type' : 'plenary', 'time' : slot_time.strftime("%Y-%m-%d"), 'duration': '08:00:00', } # unauthorized post prior_slotcount = meeting.timeslot_set.count() self.client.login(username="ad", password="ad+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 403) self.assertEqual(meeting.timeslot_set.count(),prior_slotcount) # create slot self.client.login(username="secretary", password="secretary+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 201) self.assertTrue(meeting.timeslot_set.filter(time=slot_time)) self.assertEqual(meeting.timeslot_set.count(),prior_slotcount+1) def test_delete_slot(self): meeting = make_meeting_test_data() slot = meeting.timeslot_set.all()[0] url = urlreverse("ietf.meeting.ajax.timeslot_sloturl", kwargs=dict(num=meeting.number, slotid=slot.pk)) # unauthorized delete self.client.login(username="ad", password="ad+password") r = self.client.delete(url) self.assertEqual(r.status_code, 403) # delete self.client.login(username="secretary", password="secretary+password") self.client.delete(url) self.assertTrue(not meeting.timeslot_set.filter(pk=slot.pk)) def test_schedule_json(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.ajax.schedule_infourl", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) r = self.client.get(url) info = r.json() self.assertEqual(info["schedule_id"], meeting.schedule.pk) def test_create_new_schedule(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.ajax.schedule_infosurl", kwargs=dict(num=meeting.number)) post_data = { 'name': 'new-schedule', } # unauthorized post self.client.login(username="plain", password="plain+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 403) self.assertTrue(not meeting.schedule_set.filter(name='new-schedule')) # create new schedule self.client.login(username="secretary", password="secretary+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 302) self.assertTrue(meeting.schedule_set.filter(name='new-schedule')) def test_update_meeting_schedule(self): meeting = make_meeting_test_data() self.assertTrue(meeting.schedule.visible) url = urlreverse("ietf.meeting.ajax.schedule_infourl", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) post_data = { 'visible': 'false', 'name': 'new-test-name', } # unauthorized posts self.client.logout() r = self.client.post(url, post_data) self.assertEqual(r.status_code, 403) self.client.login(username="ad", password="ad+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 403) # change schedule self.client.login(username="secretary", password="secretary+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 302) changed_schedule = Schedule.objects.get(pk=meeting.schedule.pk) self.assertTrue(not changed_schedule.visible) self.assertEqual(changed_schedule.name, "new-test-name") def test_delete_schedule(self): meeting = make_meeting_test_data() url = urlreverse("ietf.meeting.ajax.schedule_infourl", kwargs=dict(num=meeting.number, owner=meeting.schedule.owner_email(), name=meeting.schedule.name)) # unauthorized delete self.client.login(username="plain", password="plain+password") r = self.client.delete(url) self.assertEqual(r.status_code, 403) # delete self.client.login(username="secretary", password="secretary+password") r = self.client.delete(url) self.assertEqual(r.status_code, 200) self.assertTrue(not Schedule.objects.filter(pk=meeting.schedule.pk)) def test_set_meeting_schedule(self): meeting = make_meeting_test_data() schedule = meeting.schedule url = urlreverse("ietf.meeting.ajax.meeting_json", kwargs=dict(num=meeting.number)) post_data = { "schedule": "", } # unauthorized post self.client.login(username="ad", password="ad+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 403) # clear self.client.login(username="secretary", password="secretary+password") r = self.client.post(url, post_data) self.assertEqual(r.status_code, 200) self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule) # set schedule - first fail with non-public post_data = { "schedule": schedule.name, } schedule.public = False schedule.save() r = self.client.post(url, post_data) self.assertTrue(r.status_code != 200) self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule) # then go through with public schedule.public = True schedule.save() # Setting a meeting as official no longer sends mail immediately prior_length= len(outbox) r = self.client.post(url, post_data) self.assertEqual(r.status_code, 200) self.assertEqual(Meeting.objects.get(pk=meeting.pk).schedule, schedule) self.assertEqual(len(outbox),prior_length) def test_read_only(self): meeting = make_meeting_test_data() # Secretariat self.client.login(username="secretary", password="secretary+password") url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name); r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info['secretariat'], True) self.assertEqual(urlsplit(info['owner_href'])[2], "/person/%s.json" % meeting.schedule.owner_id) self.assertEqual(info['read_only'], True) self.assertEqual(info['save_perm'], True) # owner self.client.login(username=meeting.schedule.owner.user.username, password=meeting.schedule.owner.user.username+"+password") url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name); r = self.client.get(url) self.assertEqual(r.status_code, 200) info = r.json() self.assertEqual(info['secretariat'], False) self.assertEqual(info['read_only'], False) self.assertEqual(info['save_perm'], False) def test_update_timeslot_pinned(self): meeting = make_meeting_test_data() scheduled = SchedTimeSessAssignment.objects.filter( session__meeting=meeting, session__group__acronym="mars").first() url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk) post_data = { "pinned": True } # unauthorized post gets failure (no redirect) r = self.client.put(url, post_data) self.assertEqual(r.status_code, 403, "post to %s should have failed, no permission, got: %u/%s" % (url, r.status_code, r.content)) self.assertTrue(not SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned) # set pinned meeting.schedule.owner = Person.objects.get(user__username="secretary") meeting.schedule.save() # need to rebuild URL, since the schedule owner has changed. url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk) self.client.login(username="secretary", password="secretary+password") r = self.client.put(url, post_data) self.assertEqual(r.status_code, 200, "post to %s should have worked, but got: %u/%s" % (url, r.status_code, r.content)) self.assertTrue(SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned) class TimeSlotEditingApiTests(TestCase): def test_manipulate_timeslot(self): meeting = make_meeting_test_data() slot = meeting.timeslot_set.all()[0] self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type_id,'regular') url = urlreverse("ietf.meeting.ajax.timeslot_sloturl", kwargs=dict(num=meeting.number, slotid=slot.pk)) modify_post_data = { "purpose" : "plenary" } # Fail as non-secretariat self.client.login(username="plain", password="plain+password") r = self.client.post(url, modify_post_data) self.assertEqual(r.status_code, 403) slot.refresh_from_db() self.assertEqual(slot.type_id, 'regular') # Successful change of purpose self.client.login(username="secretary", password="secretary+password") r = self.client.post(url, modify_post_data) self.assertEqual(r.status_code, 200) slot.refresh_from_db() self.assertEqual(slot.type_id, 'plenary')