canvas-lms/spec/models/calendar_event_spec.rb

1290 lines
55 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
describe CalendarEvent do
before(:once) do
Account.find_or_create_by!(id: 0).update(name: "Dummy Root Account", workflow_state: "deleted", root_account_id: nil)
end
it "sanitizes description" do
course_model
@c = CalendarEvent.new
@c.description = "<a href='#' onclick='alert(12);'>only this should stay</a>"
@c.context_id = @course.id
@c.context_type = "Course"
@c.save!
expect(@c.description).to eql("<a href=\"#\">only this should stay</a>")
end
describe "default_values" do
before(:once) do
course_model
@original_start_at = Time.at(1_220_443_500) # 3 Sep 2008 12:05pm (UTC)
@original_end_at = @original_start_at + 2.hours
# Create the initial event
@event = calendar_event_model(
start_at: @original_start_at,
end_at: @original_end_at,
time_zone_edited: "Mountain Time (US & Canada)"
)
end
it "gets localized start_at" do
df = "%Y-%m-%d %H:%M"
expect(@event.start_at.strftime(df)).to eq "2008-09-03 12:05"
expect(@event.zoned_start_at.strftime(df)).to eq "2008-09-03 06:05"
end
it "populates missing dates" do
event_1 = calendar_event_model
event_1.start_at = @original_start_at
event_1.end_at = nil
event_1.send(:populate_missing_dates)
expect(event_1.end_at).to eql(event_1.start_at)
event_2 = calendar_event_model
event_2.start_at = nil
event_2.end_at = @original_end_at
event_2.send(:populate_missing_dates)
expect(event_2.start_at).to eql(event_2.end_at)
event_3 = calendar_event_model
event_3.start_at = @original_end_at
event_3.end_at = @original_start_at
event_3.send(:populate_missing_dates)
expect(event_3.end_at).to eql(event_3.start_at)
end
it "populates all day flag" do
midnight = Time.at(1_361_862_000) # 2013-02-26 00:00:00
event_1 = calendar_event_model(time_zone_edited: "Mountain Time (US & Canada)")
event_1.start_at = event_1.end_at = midnight
event_1.send(:populate_all_day_flag)
expect(event_1.all_day?).to be_truthy
expect(event_1.all_day_date.strftime("%Y-%m-%d")).to eq "2013-02-26"
event_2 = calendar_event_model(time_zone_edited: "Mountain Time (US & Canada)")
event_2.start_at = @original_start_at
event_2.end_at = @original_end_at
event_2.send(:populate_all_day_flag)
expect(event_2.all_day?).to be_falsey
event_3 = calendar_event_model(
start_at: midnight,
end_at: midnight + 1.hour,
time_zone_edited: "Mountain Time (US & Canada)"
)
event_3.start_at = midnight
event_3.end_at = midnight + 30.minutes
event_3.all_day = true
event_3.send(:populate_all_day_flag)
expect(event_3.all_day?).to be_truthy
expect(event_3.end_at).to eql(event_3.start_at)
end
it "retains all day flag when date is changed (calls :default_values)" do
# Flag the event as all day
@event.update({ start_at: @original_start_at, end_at: @original_end_at, all_day: true })
expect(@event.all_day?).to be_truthy
expect(@event.all_day_date.strftime("%Y-%m-%d")).to eq "2008-09-03"
expect(@event.zoned_start_at.strftime("%H:%M")).to eq "00:00"
expect(@event.end_at).to eql(@event.zoned_start_at)
# Change the date but keep the all day flag as true
@event.update({ start_at: @event.start_at - 1.day, end_at: @event.end_at - 1.day, all_day: true })
expect(@event.all_day?).to be_truthy
expect(@event.all_day_date.strftime("%Y-%m-%d")).to eq "2008-09-02"
expect(@event.zoned_start_at.strftime("%H:%M")).to eq "00:00"
expect(@event.end_at).to eql(@event.zoned_start_at)
end
it "sets blackout_date to false by default" do
expect(@event.blackout_date).to be_falsey
end
end
describe "default_values during update" do
before(:once) do
course_model
@original_start_at = Time.at(1_220_443_500) # 3 Sep 2008 12:05pm (UTC)
@original_end_at = @original_start_at + 2.hours
@midnight = Time.at(1_220_421_600)
@event = calendar_event_model(
start_at: @original_start_at,
end_at: @original_end_at,
time_zone_edited: "Mountain Time (US & Canada)"
)
end
it "does not retain all day flag when times are changed (calls :default_values)" do
# set the start and end to midnight and make sure all_day is automatically set
@event.update({ start_at: @midnight, end_at: @midnight })
expect(@event.all_day?).to be_truthy
expect(@event.all_day_date.strftime("%Y-%m-%d")).to eq "2008-09-03"
# set the start and end to different times and then make sure all_day is automatically unset
@event.update({ start_at: @original_start_at, end_at: @original_end_at })
expect(@event.all_day?).to be_falsey
end
end
context "ical" do
describe "to_ics" do
it "does not fail for null times" do
calendar_event_model(start_at: "", end_at: "")
res = @event.to_ics
expect(res).not_to be_nil
expect(res.include?("DTSTART")).to be false
end
it "does not return data for null times" do
calendar_event_model(start_at: "", end_at: "")
res = @event.to_ics(in_own_calendar: false)
expect(res).to be_nil
end
it "returns string data for events with times" do
Time.zone = "UTC"
calendar_event_model(start_at: "Sep 3 2008 11:55am", end_at: "Sep 3 2008 12:00pm")
# force known value so we can check serialization
@event.updated_at = Time.at(1_220_443_500) # 3 Sep 2008 12:05pm (UTC)
res = @event.to_ics
expect(res).not_to be_nil
expect(res.include?("DTSTART:20080903T115500Z")).not_to be_nil
expect(res.include?("DTEND:20080903T120000Z")).not_to be_nil
expect(res.include?("DTSTAMP:20080903T120500Z")).not_to be_nil
end
it "returns string data for events with times in correct tz" do
Time.zone = "Alaska" # -0800
calendar_event_model(start_at: "Sep 3 2008 11:55am", end_at: "Sep 3 2008 12:00pm")
# force known value so we can check serialization
@event.updated_at = Time.at(1_220_472_300) # 3 Sep 2008 12:05pm (AKDT)
res = @event.to_ics
expect(res).not_to be_nil
expect(res.include?("DTSTART:20080903T195500Z")).not_to be_nil
expect(res.include?("DTEND:20080903T200000Z")).not_to be_nil
expect(res.include?("DTSTAMP:20080903T200500Z")).not_to be_nil
end
it "returns data for events with times" do
Time.zone = "UTC"
calendar_event_model(start_at: "Sep 3 2008 11:55am", end_at: "Sep 3 2008 12:00pm")
# force known value so we can check serialization
@event.updated_at = Time.at(1_220_443_500) # 3 Sep 2008 12:05pm (UTC)
res = @event.to_ics(in_own_calendar: false)
expect(res).not_to be_nil
expect(res.dtstart.tz_utc).to be true
expect(res.dtstart.strftime("%Y-%m-%dT%H:%M:%S")).to eq Time.zone.parse("Sep 3 2008 11:55am").in_time_zone("UTC").strftime("%Y-%m-%dT%H:%M:00")
expect(res.dtend.tz_utc).to be true
expect(res.dtend.strftime("%Y-%m-%dT%H:%M:%S")).to eq Time.zone.parse("Sep 3 2008 12:00pm").in_time_zone("UTC").strftime("%Y-%m-%dT%H:%M:00")
expect(res.dtstamp.tz_utc).to be true
expect(res.dtstamp.strftime("%Y-%m-%dT%H:%M:%S")).to eq Time.zone.parse("Sep 3 2008 12:05pm").in_time_zone("UTC").strftime("%Y-%m-%dT%H:%M:00")
end
it "returns data for events with times in correct tz" do
Time.zone = "Alaska" # -0800
calendar_event_model(start_at: "Sep 3 2008 11:55am", end_at: "Sep 3 2008 12:00pm")
# force known value so we can check serialization
@event.updated_at = Time.at(1_220_472_300) # 3 Sep 2008 12:05pm (AKDT)
res = @event.to_ics(in_own_calendar: false)
expect(res).not_to be_nil
expect(res.dtstart.tz_utc).to be true
expect(res.dtstart.strftime("%Y-%m-%dT%H:%M:%S")).to eq Time.zone.parse("Sep 3 2008 11:55am").in_time_zone("UTC").strftime("%Y-%m-%dT%H:%M:00")
expect(res.dtend.tz_utc).to be true
expect(res.dtend.strftime("%Y-%m-%dT%H:%M:%S")).to eq Time.zone.parse("Sep 3 2008 12:00pm").in_time_zone("UTC").strftime("%Y-%m-%dT%H:%M:00")
expect(res.dtend.tz_utc).to be true
expect(res.dtstamp.strftime("%Y-%m-%dT%H:%M:%S")).to eq Time.zone.parse("Sep 3 2008 12:05pm").in_time_zone("UTC").strftime("%Y-%m-%dT%H:%M:00")
end
it "does not fail with no date for all_day event" do
res = calendar_event_model(all_day: true).to_ics
expect(res).not_to be_nil
end
it "returns string dates for all_day events" do
calendar_event_model(start_at: "Sep 3 2008 12:00am")
expect(@event.all_day).to be(true)
expect(@event.end_at).to eql(@event.start_at)
res = @event.to_ics
expect(res.include?("DTSTART;VALUE=DATE:20080903")).not_to be_nil
expect(res.include?("DTEND;VALUE=DATE:20080903")).not_to be_nil
end
it "returns a plain-text description" do
calendar_event_model(start_at: "Sep 3 2008 12:00am", description: <<~HTML)
<p>
This assignment is due December 16th. <b>Please</b> do the reading.
<br/>
<a href="www.example.com">link!</a>
</p>
HTML
ev = @event.to_ics(in_own_calendar: false)
expect(ev.description).to match_ignoring_whitespace("This assignment is due December 16th. Please do the reading. [link!](www.example.com)")
expect(ev.x_alt_desc.first).to eq @event.description
end
it "does not add verifiers to files unless course or attachment is public" do
attachment_model(context: course_factory)
html = %(<div><a href="/courses/#{@course.id}/files/#{@attachment.id}/download?wrap=1">here</a></div>)
calendar_event_model(start_at: "Sep 3 2008 12:00am", description: html)
ev = @event.to_ics(in_own_calendar: false)
expect(ev.description).to_not include("verifier")
@attachment.file_state = "public"
@attachment.save!
AdheresToPolicy::Cache.clear
ev = @event.to_ics(in_own_calendar: false)
expect(ev.description).to include("verifier")
@attachment.file_state = "hidden"
@attachment.save!
@course.offer
@course.is_public = true
@course.save!
AdheresToPolicy::Cache.clear
ev = @event.to_ics(in_own_calendar: false)
expect(ev.description).to include("verifier")
end
it "works with media comments in course section events" do
course_model
@course.offer
@course.is_public = true
@course.media_objects.create!(media_id: "0_12345678")
event = @course.default_section.calendar_events.create!(start_at: "Sep 3 2008 12:00am",
description: %(<p><a id="media_comment_0_12345678" class="instructure_inline_media_comment video_comment" href="/media_objects/0_12345678">media comment</a></p>))
event.effective_context_code = @course.asset_string
event.save!
ics = event.to_ics
expect(ics.gsub(/\s+/, "")).to include("/courses/#{@course.id}/media_download?entryId=0_12345678")
end
it "adds a course code to the summary of an event that has a course as an effective_context" do
course_model
calendar_event_model(start_at: "Sep 3 2008 12:00am")
@event.effective_context_code = @course.asset_string
ics = @event.to_ics
expect(ics).to include("SUMMARY:#{@event.title} [#{@course.course_code}]")
end
it "adds a course code to the summary of an event that has a course as its effective_context's context" do
course_model
group(context: @course)
calendar_event_model(start_at: "Sep 3 2008 12:00am")
@event.effective_context_code = @group.asset_string
ics = @event.to_ics
expect(ics).to include("SUMMARY:#{@event.title} [#{@course.course_code}]")
end
end
end
context "root account" do
it "sets the root_account when the context is an appointment_group" do
course = Course.create!
ag = AppointmentGroup.create(title: "test", contexts: [course])
ag.publish!
appointment = ag.appointments.create(start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00")
expect(appointment.root_account_id).to eq ag.context.root_account_id
end
it "sets the root_account when the context is a course" do
course = Course.create!
appointment = CalendarEvent.create!(title: "test", context: course)
expect(appointment.root_account_id).to eq course.root_account_id
end
it "sets the root_account when the context is a user if the effective context is set and is not a user" do
user = User.create!
course = Course.create!
appointment = CalendarEvent.create!(title: "test", context: user, effective_context_code: course.asset_string)
expect(appointment.root_account_id).to eq course.root_account_id
end
it "sets the root_account_id to 0 when the context is a user and there is no effective context" do
user = User.create!
appointment = CalendarEvent.create!(title: "test", context: user)
expect(appointment.root_account_id).to eq 0
end
it "sets the root_account when the context is a group" do
account = Account.create!
group = Group.create!(context: account)
appointment = CalendarEvent.create!(title: "test", context: group)
expect(appointment.root_account_id).to eq group.root_account_id
end
it "sets the root_account when the context is a course_section" do
course = Course.create!
section = course.course_sections.create!
appointment = CalendarEvent.create!(title: "test", context: section)
expect(appointment.root_account_id).to eq section.root_account_id
end
it "sets the root_account when assignment_group is not yet assigned to a course context" do
course = Course.create!
ag = AppointmentGroup.create(
title: "test",
contexts: [course],
new_appointments: {
appt01: [Time.zone.now, Time.zone.now]
}
)
expect(ag.appointments[0].root_account_id).to eq course.root_account_id
end
end
context "for_user_and_context_codes" do
before :once do
course_with_student(active_all: true)
@student = @user
@e1 = @course.calendar_events.create!
@e2 = @student.calendar_events.create!
@e3 = Course.create.calendar_events.create!
end
it "returns events explicitly tied to the contexts" do
expect(CalendarEvent.for_user_and_context_codes(@student, [@course.asset_string]).sort_by(&:id))
.to eql [@e1]
expect(CalendarEvent.for_user_and_context_codes(@student, [@course.asset_string, @student.asset_string]).sort_by(&:id))
.to eql [@e1, @e2]
end
it "returns events implicitly tied to the contexts (via effective_context_string)" do
@teacher = user_factory
@course.enroll_teacher(@teacher).accept!
course1 = @course
course_with_teacher(user: @teacher)
course2, @course = @course, course1
g1 = AppointmentGroup.create!(title: "foo", contexts: [course1, course2])
g1.publish!
ae1 = g1.appointments.create!
a1 = ae1.reserve_for(@student, @student)
g2 = AppointmentGroup.create!(title: "foo", contexts: [@course], sub_context_codes: [@course.default_section.asset_string])
g2.publish!
ae2 = g2.appointments.create!
a2 = ae2.reserve_for(@student, @student)
g3 = AppointmentGroup.create!(title: "foo", contexts: [@course])
g3.publish!
ae3 = g3.appointments.create!
pe = @course.calendar_events.create!
section = @course.default_section
se = pe.child_events.build
se.context = section
se.save!
expect(CalendarEvent.for_user_and_context_codes(@student, [@student.asset_string]).sort_by(&:id))
.to eql [@e2] # none of the appointments even though they technically are on the user
expect(CalendarEvent.for_user_and_context_codes(@student, [section.asset_string]).sort_by(&:id))
.to eql [] # none of the appointments even though they technically are on the section
expect(CalendarEvent.for_user_and_context_codes(@student, [@course.asset_string, @student.asset_string]).sort_by(&:id))
.to eql [@e1, @e2, a1, a2, pe, se]
expect(CalendarEvent.for_user_and_context_codes(@student, [@course.asset_string]).sort_by(&:id))
.to eql [@e1, a1, a2, pe, se]
expect(CalendarEvent.for_user_and_context_codes(@student, [@course.asset_string]).events_without_child_events.sort_by(&:id))
.to eql [@e1, a1, a2, se]
expect(CalendarEvent.for_user_and_context_codes(@student, [g1.asset_string, g2.asset_string, g3.asset_string]).sort_by(&:id))
.to eql [ae1, ae2, ae3]
expect(CalendarEvent.for_user_and_context_codes(@teacher, [g1.asset_string, g2.asset_string, g3.asset_string]).events_with_child_events.sort_by(&:id))
.to eql [ae1, ae2]
end
end
context "notifications" do
before :once do
Notification.create(name: "New Event Created", category: "TestImmediately")
Notification.create(name: "Event Date Changed", category: "TestImmediately")
course_with_student(active_all: true)
@teacher = user_factory(active_all: true)
@course.enroll_teacher(@teacher).accept!
communication_channel(@student, { username: "test_channel_email_#{user_factory.id}@test.com", active_cc: true })
end
context "with calendar event created" do
before :once do
course_with_student(active_all: true)
course_with_observer(active_all: true, active_cc: true, associated_user_id: @student.id, course: @course)
@event1 = @course.calendar_events.build(title: "test")
@event1.updating_user = @teacher
@event1.save!
end
context "creation notification" do
before :once do
@users = @event1.messages_sent["New Event Created"].map(&:user_id)
end
it "sends to participants", priority: "1" do
expect(@event1.messages_sent).to include("New Event Created")
expect(@users).to include(@student.id)
end
it "has correct URL" do
@event1.messages_sent["New Event Created"].each do |message|
expect(message.url).to include "/courses/#{@course.id}/calendar_events/#{@event1.id}"
end
end
it "does not send to creating teacher" do
expect(@users).not_to include(@teacher.id)
end
it "sends to observers" do
expect(@users).to include(@observer.id)
end
end
context "with event date edited" do
before :once do
@event1.update(start_at: Time.now, end_at: Time.now)
end
context "edit notification" do
before :once do
@users = @event1.messages_sent["Event Date Changed"].map(&:user_id)
end
it "sends to participants", priority: "1" do
expect(@event1.messages_sent).to include("Event Date Changed")
expect(@users).to include(@student.id)
end
it "has correct url" do
@event1.messages_sent["Event Date Changed"].each do |message|
expect(message.url).to include "/courses/#{@course.id}/calendar_events/#{@event1.id}"
end
end
it "does not send to editing teacher" do
expect(@users).not_to include(@teacher.id)
end
it "sends to observers" do
expect(@users).to include(@observer.id)
end
end
end
end
it "does not send notifications to participants if hidden" do
course_with_student(active_all: true)
event = @course.calendar_events.build(title: "test", child_event_data: [{ start_at: "2012-01-01", end_at: "2012-01-02", context_code: @course.default_section.asset_string }])
event.updating_user = @teacher
event.save!
expect(event.messages_sent).to be_empty
event.update_attribute(:child_event_data, [{ start_at: "2012-01-02", end_at: "2012-01-03", context_code: @course.default_section.asset_string }])
expect(event.messages_sent).to be_empty
end
end
context "appointments" do
before :once do
course_with_student(active_all: true)
@student1 = @user
@other_section = @course.course_sections.create!
@other_course = Course.create!
@ag = AppointmentGroup.create(title: "test", contexts: [@course])
@ag.publish!
@appointment = @ag.appointments.create(start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00")
end
context "notifications" do
before do
Notification.create(name: "Appointment Canceled By User", category: "TestImmediately")
Notification.create(name: "Appointment Deleted For User", category: "TestImmediately")
Notification.create(name: "Appointment Reserved By User", category: "TestImmediately")
Notification.create(name: "Appointment Reserved For User", category: "TestImmediately")
@teacher = user_factory(active_all: true)
@course.enroll_teacher(@teacher).accept!
student_in_course(course: @course, active_all: true)
@student2 = @user
c1 = group_category
@group = c1.groups.create(context: @course)
@group.users << @student1 << @student2
@ag2 = AppointmentGroup.create!(title: "test", contexts: [@course], sub_context_codes: [c1.asset_string])
@ag2.publish!
@appointment2 = @ag2.appointments.create(start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00")
course_with_observer(active_all: true, course: @course, associated_user_id: @student1.id)
[@teacher, @student1, @student2, @observer].each do |user|
communication_channel(user, { username: "test_channel_email_#{user.id}@test.com", active_cc: true })
end
@expected_users = [@teacher.id, @student1.id, @student2.id, @observer.id].sort
end
def message_recipients_for(notification_name)
Message.where(notification_id: BroadcastPolicy.notification_finder.by_name(notification_name), user_id: @expected_users).pluck(:user_id).sort
end
it "includes course_ids from appointment_groups" do
reservation = @appointment2.reserve_for(@group, @student1)
expect(reservation.course_broadcast_data).to eql({ root_account_id: @course.root_account_id, course_ids: [@course.id] })
end
it "includes multiple course_ids" do
course2 = @course.root_account.courses.create!(name: "course2", workflow_state: "available")
course2.enroll_teacher(@teacher).accept!
ag = AppointmentGroup.create!(title: "test", contexts: [@course, course2])
appointment = ag.appointments.create!(start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00")
reservation = appointment.reserve_for(@student1, @student1)
expect(reservation.course_broadcast_data).to eql({ root_account_id: @course.root_account_id, course_ids: [@course.id, course2.id] })
end
it "notifies all participants except the person reserving", priority: "1" do
@appointment2.reserve_for(@group, @student1)
expect(message_recipients_for("Appointment Reserved For User")).to eq @expected_users - [@student1.id, @teacher.id]
end
it "notifies all participants except the person canceling the reservation" do
reservation = @appointment2.reserve_for(@group, @student1)
reservation.updating_user = @student1
reservation.destroy
expect(message_recipients_for("Appointment Deleted For User")).to eq @expected_users - [@student1.id, @teacher.id]
end
it "notifies participants if teacher deletes the appointment time slot", priority: "1" do
@appointment2.reserve_for(@group, @student1)
@appointment2.updating_user = @teacher
@appointment2.destroy
expect(message_recipients_for("Appointment Deleted For User")).to eq @expected_users - [@teacher.id]
end
it "notifies all participants when the the time slot is canceled", priority: "1" do
@appointment2.reserve_for(@group, @student1)
@appointment2.updating_user = @teacher
user_evt = CalendarEvent.where(context_type: "Group").first
user_evt.updating_user = @teacher
user_evt.destroy
expect(message_recipients_for("Appointment Deleted For User")).to eq @expected_users - [@teacher.id]
end
it "notifies admins and observers when a user reserves", priority: "1" do
reservation = @appointment.reserve_for(@student1, @student1)
messages = reservation.messages_sent["Appointment Reserved By User"]
expect(messages).to be_present
expect(messages.first.root_account_id).to eq Account.default.id
expect(messages.map(&:user_id).sort.uniq).to eql (@course.instructors.map(&:id) + [@observer.id]).sort
end
it "notifies admins and observers when a user reserves a group appointment" do
reservation = @appointment2.reserve_for(@group, @student1)
expect(reservation.messages_sent).to include("Appointment Reserved By User")
expect(reservation.messages_sent["Appointment Reserved By User"].map(&:user_id).sort.uniq).to eql (@course.instructors.map(&:id) + [@observer.id]).sort
end
it "notifies admins and observers when a user cancels", priority: "1" do
reservation = @appointment.reserve_for(@student1, @student1)
reservation.updating_user = @student1
reservation.destroy
expect(reservation.messages_sent).to include("Appointment Canceled By User")
expect(reservation.messages_sent["Appointment Canceled By User"].map(&:user_id).sort.uniq).to eql (@course.instructors.map(&:id) + [@observer.id]).sort
end
end
it "updates the appointment group when a related event is deleted" do
ag = AppointmentGroup.create!(
title: "testing...",
contexts: [@course],
participants_per_appointment: 2,
new_appointments: [
["2012-01-01 13:00:00", "2012-01-01 14:00:00"],
["2012-01-02 13:00:00", "2012-01-02 14:00:00"]
]
)
expect(AppointmentGroup.find(ag.id).start_at).to eq("2012-01-01 13:00:00")
ag.appointments.first.destroy
expect(AppointmentGroup.find(ag.id).start_at).to eq("2012-01-02 13:00:00")
end
it "allows multiple participants in an appointment, up to the limit" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
participants_per_appointment: 2,
new_appointments: [["2012-01-01 13:00:00", "2012-01-01 14:00:00"]])
ag.publish!
appointment = ag.appointments.first
student_in_course(course: @course, active_all: true)
@other_student = @user
student_in_course(course: @course, active_all: true)
@unlucky_student = @user
expect(appointment.reserve_for(@student1, @student1)).not_to be_nil
expect(appointment.reserve_for(@other_student, @other_student)).not_to be_nil
expect { appointment.reserve_for(@unlucky_student, @unlucky_student) }.to raise_error(CalendarEvent::ReservationError)
end
it "gives preference to the calendar's appointment limit" do
ag = AppointmentGroup.create!(
title: "testing...",
contexts: [@course],
participants_per_appointment: 2,
new_appointments: [["2012-01-01 13:00:00", "2012-01-01 14:00:00"]]
)
ag.publish!
appointment = ag.appointments.first
appointment.participants_per_appointment = 3
appointment.save!
s1, s2, s3 = Array.new(3) do
student_in_course(course: @course, active_all: true)
@user
end
expect(appointment.reserve_for(@student1, @student1)).not_to be_nil
expect(appointment.reserve_for(s1, s1)).not_to be_nil
expect(appointment.reserve_for(s2, s2)).not_to be_nil
expect { appointment.reserve_for(s3, s3) }.to raise_error(CalendarEvent::ReservationError)
# should be able to unset the participant limit too
appointment.participants_per_appointment = nil
appointment.save!
expect(appointment.reserve_for(s3, s3)).not_to be_nil
end
it "reverts to the appointment group's participant_limit when appropriate" do
ag = AppointmentGroup.create!(
title: "testing...",
contexts: [@course],
participants_per_appointment: 2,
new_appointments: [["2012-01-01 13:00:00", "2012-01-01 14:00:00"]]
)
ag.publish!
appointment = ag.appointments.first
appointment.participants_per_appointment = 3
appointment.save!
expect(appointment.participants_per_appointment).to be 3
appointment.participants_per_appointment = 2
appointment.save!
expect(appointment.read_attribute(:participants_per_limit)).to be_nil
expect(appointment.override_participants_per_appointment?).to be_falsey
expect(appointment.participants_per_appointment).to be 2
end
it "does not let participants exceed max_appointments_per_participant" do
ag = AppointmentGroup.create(
title: "test",
contexts: [@course],
max_appointments_per_participant: 1,
new_appointments: [["2012-01-01 12:00:00", "2012-01-01 13:00:00"], ["2012-01-01 13:00:00", "2012-01-01 14:00:00"]]
)
ag.publish!
appointment = ag.appointments.first
appointment2 = ag.appointments.last
appointment.reserve_for(@student1, @student1)
expect { appointment2.reserve_for(@student1, @student1) }.to raise_error(CalendarEvent::ReservationError)
end
it "cancels existing reservations if cancel_existing = true and the appointment is in the future" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
max_appointments_per_participant: 1,
new_appointments: [[1.hour.from_now, 2.hours.from_now], [3.hours.from_now, 4.hours.from_now]])
ag.publish!
appointment = ag.appointments.first
appointment2 = ag.appointments.last
r1 = appointment.reserve_for(@student1, @student1)
expect { appointment2.reserve_for(@student1, @student1, cancel_existing: true) }.not_to raise_error
expect(r1.reload).to be_deleted
end
it "refuses to cancel existing reservations if cancel_existing = true and the appointment is in the past" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
max_appointments_per_participant: 1,
new_appointments: [[2.hours.ago, 1.hour.ago], [1.hour.from_now, 2.hours.from_now]])
ag.publish!
appointment = ag.appointments.first
appointment2 = ag.appointments.last
r1 = appointment.reserve_for(@student1, @student1)
expect { appointment2.reserve_for(@student1, @student1, cancel_existing: true) }.to raise_error(CalendarEvent::ReservationError)
expect(r1.reload).not_to be_deleted
end
it "saves comments with appointment" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
max_appointments_per_participant: 1,
new_appointments: [["2012-01-01 12:00:00",
"2012-01-01 13:00:00"],
["2012-01-01 13:00:00",
"2012-01-01 14:00:00"]])
ag.publish!
appointment = ag.appointments.first
r1 = appointment.reserve_for(@student1, @student1, comments: "my appointment notes")
r1.reload
expect(r1.comments).to eq("my appointment notes")
end
it "enforces the section" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course.course_sections.create],
new_appointments: [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
ag.publish!
appointment = ag.appointments.first
expect { appointment.reserve_for(@student1, @student1) }.to raise_error(CalendarEvent::ReservationError)
end
it "enforces the group category" do
teacher = user_factory(active_all: true)
@course.enroll_teacher(teacher).accept!
c1 = group_category
g1 = c1.groups.create(context: @course)
c2 = group_category(name: "bar")
g2 = c2.groups.create(context: @course)
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
sub_context_codes: [c1.asset_string],
new_appointments: [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
appointment = ag.appointments.first
ag.publish!
expect { appointment.reserve_for(g2, teacher) }.to raise_error(CalendarEvent::ReservationError)
expect { appointment.reserve_for(g1, teacher) }.not_to raise_error
end
it "only accepts users with StudentEnrollments as valid user participants" do
expect(@ag.eligible_participant?(@student1)).to be_truthy
expect { @appointment.reserve_for(@student1, @student1) }.not_to raise_error
# both a student and a teacher
student_in_course(course: @course, active_all: true)
@user.teacher_enrollments.create!(course: @course)
expect(@ag.eligible_participant?(@user)).to be_truthy
expect { @appointment.reserve_for(@user, @user) }.not_to raise_error
# just a teacher
user_factory(active_all: true)
@course.enroll_teacher(@user).accept!
expect(@ag.eligible_participant?(@user)).to be_falsey
expect { @appointment.reserve_for(@user, @user) }.to raise_error(CalendarEvent::ReservationError)
end
it "locks the appointment once it is reserved" do
expect(@appointment).to be_active
expect(@appointment.reserve_for(@student1, @student1)).not_to be_nil
expect(@appointment).to be_locked
end
it "unlocks the appointment when the last reservation is canceled" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
participants_per_appointment: 2,
new_appointments: [["2012-01-01 13:00:00", "2012-01-01 14:00:00"]])
appointment = ag.appointments.first
student_in_course(course: @course, active_all: true)
@other_student = @user
expect(appointment).to be_active
r1 = appointment.reserve_for(@student1, @student1).reload
expect(appointment).to be_locked
r2 = appointment.reserve_for(@other_student, @other_student).reload
r2.destroy
expect(appointment.reload).to be_locked
r1.destroy
expect(appointment.reload).to be_active
end
it "copies the group attributes to the initial appointments" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
description: "hello world",
new_appointments: [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]])
e = ag.appointments.first
expect(e.title).to eql "test"
expect(e.description).to eql "hello world"
end
it "copies changed group attributes to existing appointments" do
@ag.update(title: "changed!", description: "test123")
e = @ag.appointments.first.reload
expect(e.title).to eql "changed!"
expect(e.description).to eql "test123"
end
it "does not copy group description if appointment is overridden" do
@appointment.description = "pizza party"
@appointment.save!
@ag.description = "boring meeting"
@ag.save!
expect(@appointment.description).to eq "pizza party"
expect(@ag.description).to eq "boring meeting"
end
it "copies the group attributes to subsequent appointments" do
ag = AppointmentGroup.create(title: "test", contexts: [@course])
ag.update(
title: "haha",
new_appointments: [["2012-01-01 12:00:00", "2012-01-01 13:00:00"]]
)
e = ag.appointments.first
expect(e.title).to eql "haha"
end
it "ignores changes to locked attributes on the appointment" do
@appointment.update(start_at: "2012-01-01 12:30:00", title: "you wish")
expect(@appointment.title).to eql "test"
expect(@appointment.start_at).to eql Time.parse("2012-01-01 12:30:00Z")
end
it "allows a user to re-reserve a slot after canceling" do
ag = AppointmentGroup.create(title: "test",
contexts: [@course],
participants_per_appointment: 1,
new_appointments: [["2012-01-01 13:00:00", "2012-01-01 14:00:00"]])
appointment = ag.appointments.first
r1 = appointment.reserve_for(@student1, @student1).reload
expect(ag.reload.available_slots).to be 0
r1.destroy
expect(ag.reload.available_slots).to be 1
expect { appointment.reserve_for(@student1, @student1) }.not_to raise_error
expect(ag.reload.available_slots).to be 0
end
it "always allows editing the description on an appointment" do
@appointment.update_attribute :workflow_state, "locked"
@appointment.description = "bacon"
@appointment.save!
expect(@appointment.description).to eq "bacon"
end
end
context "child_events" do
it "deletes child events when deleting the parent" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.save!
@event.destroy
expect(@event.reload).to be_deleted
expect(child.reload).to be_deleted
end
it "deletes the parent event after the last child event is deleted" do
calendar_event_model
sec2 = @course.course_sections.create! name: "sec2"
child1 = @event.child_events.create! context: @course.default_section, start_at: 1.day.from_now
child2 = @event.child_events.create! context: sec2, start_at: 2.days.from_now
child1.destroy
expect(@event.reload).to be_active
child2.destroy
expect(@event.reload).to be_deleted
end
context "bulk updating" do
before :once do
course_with_teacher(active_all: true)
end
it "validates child events" do
expect do
@course.calendar_events.create! title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string }
]
end.to raise_error(/Can't update child events unless an updating_user is set/)
expect do
event = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: "invalid_1" }
]
event.updating_user = @user
event.save!
end.to raise_error(/Invalid child event context/)
expect do
other_section = Course.create!.default_section
event = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: other_section.asset_string }
]
event.updating_user = @user
event.save!
end.to raise_error(/Invalid child event context/)
expect do
event = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-01 13:00:00", end_at: "2012-01-01 14:00:00", context_code: @course.default_section.asset_string }
]
event.updating_user = @user
event.save!
end.to raise_error(/Duplicate child event contexts/)
end
it "allows user to update child_events only where they have permissions" do
section1 = @course.default_section
section2 = @course.course_sections.create!
admin = account_admin_user
event = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: section1.asset_string },
{ start_at: "2012-01-01 13:00:00", end_at: "2012-01-01 14:00:00", context_code: section2.asset_string }
]
event.updating_user = admin
event.save!
teacher = user_factory(active_all: true)
limited_teacher_role = custom_teacher_role("Limited teacher", account: Account.default)
RoleOverride.create!(context: Account.default, permission: "manage_calendar", role: limited_teacher_role, enabled: false)
@course.enroll_teacher(teacher, enrollment_state: :active, section: section1)
@course.enroll_teacher(teacher, role: limited_teacher_role, enrollment_state: :active, section: section2)
teacher.enrollments.update_all(limit_privileges_to_course_section: true)
event.child_event_data = [
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: section1.asset_string },
{ start_at: "2012-01-01 13:00:00", end_at: "2012-01-01 14:00:00", context_code: section2.asset_string }
]
event.updating_user = teacher
event.save!
expect do
event.child_event_data = [
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: section1.asset_string },
{ start_at: "2012-01-02 13:00:00", end_at: "2012-01-02 14:00:00", context_code: section2.asset_string }
]
event.updating_user = teacher
event.save!
end.to raise_error(/Invalid child event context/)
end
it "creates child events" do
s2 = @course.course_sections.create!
e1 = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: s2.asset_string },
]
e1.updating_user = @user
e1.save!
e1.reload
events = e1.child_events.sort_by(&:id)
expect(events.map(&:context_code)).to eql [@course.default_section.asset_string, s2.asset_string]
expect(events.map(&:effective_context_code).uniq).to eql [@course.asset_string]
expect(e1.start_at).to eql events.first.start_at
expect(e1.end_at).to eql events.last.end_at
end
it "updates child events" do
s2 = @course.course_sections.create!
s3 = @course.course_sections.create!
e1 = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: s2.asset_string },
]
e1.updating_user = @user
e1.save!
e1.reload
events1 = e1.child_events.sort_by(&:id)
e1.update child_event_data: [
{ start_at: "2012-01-01 13:00:00", end_at: "2012-01-01 14:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: s3.asset_string },
]
e1.reload
events2 = e1.child_events.sort_by(&:id)
expect(events2.size).to be 2
expect(events1.first.reload).to eql events2.first
expect(events1.last.reload).to be_deleted
end
it "does not try to migrate resources to section context" do
attachment_with_context(@course)
s2 = @course.course_sections.create!
e1 = @course.calendar_events.build title: "ohai",
description: "<img src='/courses/#{@course.id}/files/#{@attachment.id}/preview'>",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: s2.asset_string },
]
e1.updating_user = @user
e1.save!
e1.child_event_data = [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-02 22:00:00", end_at: "2012-01-02 23:00:00", context_code: s2.asset_string },
]
expect { e1.save! }.not_to raise_error
end
it "deletes all child events" do
s2 = @course.course_sections.create!
@course.course_sections.create!
e1 = @course.calendar_events.build title: "ohai",
child_event_data: [
{ start_at: "2012-01-01 12:00:00", end_at: "2012-01-01 13:00:00", context_code: @course.default_section.asset_string },
{ start_at: "2012-01-02 12:00:00", end_at: "2012-01-02 13:00:00", context_code: s2.asset_string },
]
e1.updating_user = @user
e1.save!
e1.reload
e1.update remove_child_events: true
expect(e1.child_events.reload).to be_empty
end
it "unsets all_day when deleting child events" do
s2 = @course.course_sections.create!
e1 = @course.calendar_events.create!({
title: "foo",
start_at: "2020-10-29T00:00:00.000Z",
end_at: "2020-10-29T00:00:00.000Z",
updating_user: @user,
child_event_data: [
{ start_at: "2020-10-27T10:00:00.000Z", end_at: "2020-10-27T11:00:00.000Z", context_code: @course.default_section.asset_string },
{ start_at: "2020-10-27T14:00:00.000Z", end_at: "2020-10-27T15:00:00.000Z", context_code: s2.asset_string }
]
})
e1 = CalendarEvent.find(e1.id)
e1.update({
start_at: "2020-10-27T10:00:00.000Z",
end_at: "2020-10-27T15:00:00.000Z",
remove_child_events: true
})
expect(e1.reload).not_to be_all_day
end
end
context "cascading" do
it "copies cascaded attributes when creating a child event" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.save!
expect(child.start_at).to be_nil
expect(child.title).to eql @event.title
end
it "updates cascaded attributes on the child events whenever the parent is updated" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.save!
child.reload
orig_start_at = child.start_at
@event.title = "asdf"
@event.start_at = Time.now.utc
@event.save!
expect(child.reload.title).to eql "asdf"
expect(child.start_at).to eql orig_start_at
end
it "disregards attempted changes to cascaded attributes" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.save!
child.reload
orig_start_at = child.start_at
child.title = "asdf"
child.start_at = Time.now.utc
child.save!
expect(child.title).to eql "some event"
expect(child.start_at).not_to eql orig_start_at
end
end
context "locking" do
it "copies all attributes when creating a locked child event" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.workflow_state = :locked
child.save!
expect(child.start_at).to eql @event.start_at
expect(child.title).to eql @event.title
end
it "updates locked child events whenever the parent is updated" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.workflow_state = :locked
child.save!
@event.title = "asdf"
@event.save!
expect(child.reload.title).to eql "asdf"
end
it "disregards attempted changes to locked attributes" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
child = @event.child_events.build
child.context = user_factory
child.workflow_state = :locked
child.save!
child.title = "asdf"
child.save!
expect(child.title).to eql "some event"
end
it "unlocks events when the last child is deleted" do
calendar_event_model(start_at: "Sep 3 2008", title: "some event")
@event.workflow_state = :locked
@event.save!
child = @event.child_events.build
child.context = user_factory
child.workflow_state = :locked
child.save!
child.destroy
expect(@event.reload).to be_active
expect(child.reload).to be_deleted
end
end
end
context "web_conference" do
before(:once) do
%w[big_blue_button wimba].each do |name|
plugin = PluginSetting.create!(name:)
plugin.update_attribute(:settings, { key: "value" })
end
end
let_once(:course) { course_model }
let_once(:course2) { course_model }
let_once(:group1) { group(context: course) }
let_once(:group2) { group(context: course) }
def conference(context:, user: @user, type: "BigBlueButton")
WebConference.create!(context:, user:, conference_type: type)
end
before do
@course = course
end
it "can have no conference" do
calendar_event_model
expect(@event).to be_valid
end
it "sets the conference to nil when the calendar event is destroyed" do
event = course.calendar_events.create! title: "Foo", web_conference: conference(context: course)
expect(event.web_conference.id).to eq WebConference.last.id
event.destroy!
expect(event.web_conference).to be_nil
end
context "after_save callbacks" do
it "keeps title of conference in sync with event" do
event = course.calendar_events.create! title: "Foo", web_conference: conference(context: course)
event.update! title: "updated title"
expect(event.web_conference.reload.title).to eq "updated title"
end
it "keeps date of conference in sync with event" do
event = course.calendar_events.create! title: "Foo", web_conference: conference(context: course)
start_at = 3.days.from_now
event.reload.update!(start_at:)
expect(event.web_conference.reload.user_settings[:scheduled_date]).to eq start_at
end
it "does not fail when conference does not support scheduled_date" do
event = course.calendar_events.create! title: "Foo", web_conference: conference(context: course, type: "Wimba")
start_at = 3.days.from_now
event.reload.update!(start_at:)
expect(event.reload.start_at).to eq start_at
expect(event.web_conference.reload.user_settings).not_to have_key(:scheduled_date)
end
end
context "when event has course context" do
it "can have a conference from the same course" do
event = course.calendar_events.build title: "Foo", web_conference: conference(context: course)
expect(event).to be_valid
end
it "cannot have a conference from a different course" do
event = course.calendar_events.build title: "Foo", web_conference: conference(context: course2)
expect(event).not_to be_valid
end
end
context "when event has group context" do
it "can have a conference from the same group" do
event = group1.calendar_events.build title: "Foo", web_conference: conference(context: group1)
expect(event).to be_valid
end
it "cannot have a conference from a different group" do
event = group1.calendar_events.build title: "Foo", web_conference: conference(context: group2)
expect(event).not_to be_valid
end
end
context "when event has course section context" do
it "can have a conference from the course" do
section = add_section("foo", course:)
event = section.calendar_events.build title: "Foo", web_conference: conference(context: course)
expect(event).to be_valid
end
it "can not have have a conference from a different course" do
section = add_section("foo", course:)
event = section.calendar_events.build title: "Foo", web_conference: conference(context: course2)
expect(event).not_to be_valid
end
end
end
end