canvas-lms/spec/lib/assignment_override_applica...

1274 lines
53 KiB
Ruby

#
# Copyright (C) 2012 - 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/>.
#
require_relative '../sharding_spec_helper'
describe AssignmentOverrideApplicator do
def create_group_override
@category = group_category
@group = @category.groups.create!(context: @course)
@assignment.group_category = @category
@assignment.save!
@override = assignment_override_model(:assignment => @assignment)
@override.set = @group
@override.save!
@membership = @group.add_user(@student)
end
def create_group_override_for_discussion
@category = group_category(name: "bar")
@group = @category.groups.create!(context: @course)
@assignment = create_assignment(:course => @course)
@assignment.submission_types = 'discussion_topic'
@assignment.saved_by = :discussion_topic
@discussion_topic = @course.discussion_topics.create(:message => "some message")
@discussion_topic.group_category_id = @category.id
@discussion_topic.assignment = @assignment
@discussion_topic.save!
@assignment.reload
@override = assignment_override_model(:assignment => @assignment)
@override.set = @group
@override.save!
@membership = @group.add_user(@student)
end
def create_assignment(*args)
# need to make sure it doesn't invalidate the cache right away
Timecop.freeze(5.seconds.ago) do
assignment_model(*args)
end
end
describe "assignment_overridden_for" do
before :each do
student_in_course
@assignment = create_assignment(:course => @course)
end
it "should note the user id for whom overrides were applied" do
@adhoc_override = assignment_override_model(:assignment => @assignment)
@override_student = @adhoc_override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
@adhoc_override.override_due_at(7.days.from_now)
@adhoc_override.save!
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(@overridden_assignment.overridden_for_user.id).to eq @student.id
end
it "should note the user id for whom overrides were not found" do
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(@overridden_assignment.overridden_for_user.id).to eq @student.id
end
it "should apply new overrides if an overridden assignment is overridden for a new user" do
@student1 = @student
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student1)
expect(@overridden_assignment.overridden_for_user.id).to eq @student1.id
student_in_course
@student2 = @student
expect(AssignmentOverrideApplicator).to receive(:overrides_for_assignment_and_user).with(@overridden_assignment, @student2).and_return([])
@reoverridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@overridden_assignment, @student2)
end
it "should not attempt to apply overrides if an overridden assignment is overridden for the same user" do
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(@overridden_assignment.overridden_for_user.id).to eq @student.id
expect(AssignmentOverrideApplicator).to receive(:overrides_for_assignment_and_user).never
@reoverridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@overridden_assignment, @student)
end
it "ignores soft deleted Assignment Override Students" do
now = Time.zone.now.change(usec: 0)
adhoc_override = assignment_override_model(:assignment => @assignment)
override_student = adhoc_override.assignment_override_students.create!(user: @student)
adhoc_override.override_due_at(7.days.from_now(now))
adhoc_override.save!
override_student.update!(workflow_state: 'deleted')
adhoc_override = assignment_override_model(:assignment => @assignment)
adhoc_override.assignment_override_students.create!(user: @student)
adhoc_override.override_due_at(2.days.from_now(now))
adhoc_override.save!
overriden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(overriden_assignment.due_at).to eq(adhoc_override.due_at)
end
context "give teachers the more lenient of override.due_at or assignment.due_at" do
before do
teacher_in_course
@section = @course.course_sections.create! :name => "Overridden Section"
student_in_section(@section)
@student = @user
end
def override_section(section, due)
override = assignment_override_model(:assignment => @assignment)
override.set = section
override.override_due_at(due)
override.save!
end
def setup_overridden_assignments(section_due_at, assignment_due_at)
override_section(@section, section_due_at)
@assignment.update_attribute(:due_at, assignment_due_at)
@students_assignment = AssignmentOverrideApplicator.
assignment_overridden_for(@assignment, @student)
@teachers_assignment = AssignmentOverrideApplicator.
assignment_overridden_for(@assignment, @teacher)
end
it "assignment.due_at is more lenient" do
section_due_at = 5.days.ago
assignment_due_at = nil
setup_overridden_assignments(section_due_at, assignment_due_at)
expect(@teachers_assignment.due_at.to_i).to eq assignment_due_at.to_i
expect(@students_assignment.due_at.to_i).to eq section_due_at.to_i
end
it "override.due_at is more lenient" do
section_due_at = 5.days.from_now
assignment_due_at = 5.days.ago
setup_overridden_assignments(section_due_at, assignment_due_at)
expect(@teachers_assignment.due_at.to_i).to eq section_due_at.to_i
expect(@students_assignment.due_at.to_i).to eq section_due_at.to_i
end
it "ignores assignment.due_at if all sections have overrides" do
section_due_at = 5.days.from_now
assignment_due_at = 1.year.from_now
override_section(@course.default_section, section_due_at)
setup_overridden_assignments(section_due_at, assignment_due_at)
expect(@teachers_assignment.due_at.to_i).to eq section_due_at.to_i
expect(@students_assignment.due_at.to_i).to eq section_due_at.to_i
end
end
end
describe "overrides_for_assignment_and_user" do
before :each do
student_in_course
@assignment = create_assignment(:course => @course, :due_at => 5.days.from_now)
end
context 'it works' do
it "should be serializable" do
override = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect { Marshal.dump(override) }.not_to raise_error
end
it "should cache by assignment and user" do
enable_cache do
AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(Rails.cache).to receive(:write_entry).never
AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
end
end
it "should distinguish cache by assignment" do
enable_cache do
overrides1 = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
assignment = create_assignment
expect(Rails.cache).to receive(:write_entry)
overrides2 = AssignmentOverrideApplicator.overrides_for_assignment_and_user(assignment, @student)
end
end
it "should distinguish cache by assignment version" do
Timecop.travel Time.now + 1.hour do
@assignment.due_at = 7.days.from_now
@assignment.save!
expect(@assignment.versions.count).to eq 2
enable_cache do
AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment.versions.first.model, @student)
expect(Rails.cache).to receive(:write_entry)
AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment.versions.current.model, @student)
end
end
end
it "should distinguish cache by user" do
enable_cache do
AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
user = user_model
expect(Rails.cache).to receive(:write_entry)
AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, user)
end
end
it "should order adhoc override before group override" do
@category = group_category
@group = @category.groups.create!(:context => @course)
@membership = @group.add_user(@student)
@assignment.group_category = @category
@assignment.save!
@group_override = assignment_override_model(:assignment => @assignment)
@group_override.set = @group
@group_override.save!
@adhoc_override = assignment_override_model(:assignment => @assignment)
@override_student = @adhoc_override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides.size).to eq 2
expect(overrides.first).to eq @adhoc_override
expect(overrides.last).to eq @group_override
end
it "should order group override before section overrides" do
@category = group_category
@group = @category.groups.create!(:context => @course)
@membership = @group.add_user(@student)
@assignment.group_category = @category
@assignment.save!
@section_override = assignment_override_model(:assignment => @assignment)
@section_override.set = @course.default_section
@section_override.save!
@group_override = assignment_override_model(:assignment => @assignment)
@group_override.set = @group
@group_override.save!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides.size).to eq 2
expect(overrides.first).to eq @group_override
expect(overrides.last).to eq @section_override
end
it "should order section overrides by position" # see TODO in implementation
context "sharding" do
specs_require_sharding
it "should not break when running for a teacher on a different shard" do
@shard1.activate do
@teacher = User.create!
end
teacher_in_course(:user => @teacher, :course => @course, :active_all => true)
@adhoc_override = assignment_override_model(:assignment => @assignment)
@adhoc_override.assignment_override_students.create!(:user => @student)
allow(ActiveRecord::Base.connection).to receive(:use_qualified_names?).and_return(true)
@shard1.activate do
ovs = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @teacher)
expect(ovs).to eq [@adhoc_override]
end
end
end
end
context 'adhoc overrides' do
before :each do
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
end
describe 'for students' do
it "should include adhoc override for the user" do
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to eq [@override]
end
it "should not include adhoc overrides that don't include the user" do
new_student = student_in_course
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, new_student.user)
expect(overrides).to be_empty
end
it "finds the overrides for the correct student" do
result = AssignmentOverrideApplicator::adhoc_override(@assignment, @student)
expect(result.assignment_override_id).to eq @override.id
end
it "returns AssignmentOverrideStudent" do
result = AssignmentOverrideApplicator::adhoc_override(@assignment, @student)
expect(result).to be_an_instance_of(AssignmentOverrideStudent)
end
end
describe 'for teachers' do
before { teacher_in_course(:active_all => true) }
it "works" do
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @teacher)
expect(overrides).to eq [@override]
end
it "should not duplicate adhoc overrides" do
@override_student = @override.assignment_override_students.create(user: student_in_course.user)
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @teacher)
expect(overrides).to eq [@override]
end
end
describe 'for observers' do
it "works" do
course_with_observer({:course => @course, :active_all => true})
@course.enroll_user(@observer, "ObserverEnrollment", {:associated_user_id => @student.id})
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @observer)
expect(overrides).to eq [@override]
end
end
describe 'for admins' do
it "works" do
account_admin_user
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @admin)
expect(overrides).to eq [@override]
end
end
end
context 'group overrides' do
before :each do
create_group_override
end
describe 'for students' do
it 'returns group overrides' do
result = AssignmentOverrideApplicator.group_overrides(@assignment, @student)
expect(result).to eq [@override]
end
it 'returns groups overrides for graded discussions' do
create_group_override_for_discussion
result = AssignmentOverrideApplicator.group_overrides(@assignment, @student)
expect(result).to eq [@override]
end
it "should not include group override for groups other than the user's" do
@override.set = @category.groups.create!(context: @course)
@override.save!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to be_empty
end
it "should not include group override for deleted groups" do
@group.destroy
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to be_empty
end
it "should not include group override for deleted group memberships" do
@membership.destroy
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to be_empty
end
it "should still return something when there are old deleted group overrides" do
@override.destroy!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to be_empty
override2 = assignment_override_model(:assignment => @assignment)
override2.set = @group
override2.save!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to eq [override2]
end
context "sharding" do
specs_require_sharding
it "should determine cross-shard user groups correctly" do
cs_user = @shard1.activate { User.create! }
student_in_course(:course => @course, :user => cs_user)
@group.add_user(cs_user)
result = AssignmentOverrideApplicator.group_overrides(@assignment, cs_user)
expect(result).to eq [@override]
end
end
end
describe 'for teachers' do
it 'works' do
teacher_in_course
result = AssignmentOverrideApplicator.group_overrides(@assignment, @teacher)
expect(result).to eq [@override]
end
end
describe 'for observers' do
it 'works' do
course_with_observer({:course => @course, :active_all => true})
@course.enroll_user(@observer, "ObserverEnrollment", {:associated_user_id => @student.id})
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @observer)
expect(overrides).to eq [@override]
end
end
describe 'for admins' do
it 'works' do
account_admin_user
user_session(@admin)
result = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @admin)
expect(result).to eq [@override]
end
end
end
context 'section overrides' do
before :each do
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.save!
@section2 = @course.course_sections.create!(:name => "Summer session")
@override2 = assignment_override_model(:assignment => @assignment)
@override2.set_type = 'CourseSection'
@override2.set_id = @section2.id
@override2.due_at = 7.days.from_now
@override2.save!
@student2 = student_in_section(@section2, {:active_all => true})
end
describe 'for students' do
it "returns section overrides" do
result = AssignmentOverrideApplicator::section_overrides(@assignment, @student2)
expect(result.length).to eq 1
end
it "should enforce lenient date" do
adhoc_due_at = 10.days.from_now
ao = AssignmentOverride.new()
ao.assignment = @assignment
ao.title = "ADHOC OVERRIDE"
ao.workflow_state = "active"
ao.set_type = "ADHOC"
ao.override_due_at(adhoc_due_at)
ao.save!
override_student = ao.assignment_override_students.build
override_student.user = @student2
override_student.save!
@assignment.reload
students_assignment = AssignmentOverrideApplicator.
assignment_overridden_for(@assignment, @student2)
expect(students_assignment.due_at.to_i).to eq adhoc_due_at.to_i
end
it "should include section overrides for sections with an active student enrollment" do
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student2)
expect(overrides).to eq [@override2]
end
it "should not include section overrides for sections with deleted enrollments" do
@student2.student_enrollments.first.destroy
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student2)
expect(overrides).to be_empty
end
it "should not include section overrides for sections with concluded enrollments" do
@student2.student_enrollments.first.conclude
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student2)
expect(overrides).to be_empty
end
it "should include all relevant section overrides" do
@course.enroll_student(@student, :section => @override2.set, :allow_multiple_enrollments => true)
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides.size).to eq 2
expect(overrides).to include(@override)
expect(overrides).to include(@override2)
end
it "should work even if :read_roster is disabled" do
RoleOverride.create!(:context => @course.root_account, :permission => 'read_roster',
:role => student_role, :enabled => false)
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student2)
expect(overrides).to eq [@override2]
end
it "should only use the latest due_date for student_view_student" do
due_at = 3.days.from_now
a = create_assignment(:course => @course)
cs1 = @course.course_sections.create!
override1 = assignment_override_model(:assignment => a)
override1.set = cs1
override1.override_due_at(due_at)
override1.save!
cs2 = @course.course_sections.create!
override2 = assignment_override_model(:assignment => a)
override2.set = cs2
override2.override_due_at(due_at - 1.day)
override2.save!
@fake_student = @course.student_view_student
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(a, @fake_student)
expect(overrides).to include(override1, override2)
expect(AssignmentOverrideApplicator.collapsed_overrides(a, overrides)[:due_at].to_i).to eq due_at.to_i
end
it "should not include section overrides for sections without an enrollment" do
assignment = create_assignment(:course => @course, :due_at => 5.days.from_now)
override = assignment_override_model(:assignment => assignment)
override.set = @course.course_sections.create!
override.save!
overrides = AssignmentOverrideApplicator.section_overrides(assignment, @student)
expect(overrides).to be_empty
end
end
describe 'for teachers' do
it 'works' do
teacher_in_course
result = AssignmentOverrideApplicator.section_overrides(@assignment, @teacher)
expect(result).to include(@override, @override2)
end
end
describe 'for observers' do
it 'works' do
course_with_observer({:course => @course, :active_all => true})
@course.enroll_user(@observer, "ObserverEnrollment", {:associated_user_id => @student2.id})
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @observer)
expect(overrides).to eq [@override2]
end
end
describe 'for admins' do
it 'works' do
account_admin_user
result = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @admin)
expect(result).to include(@override, @override2)
end
end
end
context '#observer_overrides' do
it "returns all dates visible to observer" do
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
course_with_observer({:course => @course, :active_all => true})
@course.enroll_user(@observer, "ObserverEnrollment", {:associated_user_id => @student.id})
@section2 = @course.course_sections.create!(:name => "Summer session")
@override2 = assignment_override_model(:assignment => @assignment)
@override2.set_type = 'ADHOC'
@override2.due_at = 7.days.from_now
@override2.save!
@override2_student = @override2.assignment_override_students.build
@student2 = student_in_section(@section2, {:active_all => true})
@override2_student.user = @student2
@override2_student.save!
@course.enroll_user(@observer, "ObserverEnrollment", {:allow_multiple_enrollments => true, :associated_user_id => @student2.id})
result = AssignmentOverrideApplicator::observer_overrides(@assignment, @observer)
expect(result.length).to eq 2
end
end
context '#has_invalid_args?' do
it "returns true with nil user" do
result = AssignmentOverrideApplicator::has_invalid_args?(@assignment, nil)
expect(result).to be_truthy
end
it "returns true for assignments with no overrides" do
result = AssignmentOverrideApplicator::has_invalid_args?(@assignment, @student)
expect(result).to be_truthy
end
it "returns false if user and overrides are valid" do
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
result = AssignmentOverrideApplicator::has_invalid_args?(@assignment, @student)
expect(result).to be_falsey
end
end
context "versioning" do
it "should use the appropriate version of an override" do
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
original_override_version_number = @override.version_number
@assignment.due_at = 3.days.from_now
@assignment.save!
@override.override_due_at(5.days.from_now)
@override.save!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides.first.version_number).to eq @override.version_number
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment.versions.first.model, @student)
expect(overrides.first.version_number).to eq original_override_version_number
end
it "should use the most-recent override version for the given assignment version" do
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
first_version = @override.version_number
@override.override_due_at(7.days.from_now)
@override.save!
second_version = @override.version_number
expect(first_version).not_to eq second_version
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides.first.version_number).to eq second_version
end
it "should exclude overrides that weren't created until a later assignment version" do
@assignment.due_at = 3.days.from_now
@assignment.save!
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment.versions.first.model, @student)
expect(overrides).to be_empty
end
it "should exclude overrides that were deleted as of the assignment version" do
@override = assignment_override_model(:assignment => @assignment)
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
@override.destroy
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment, @student)
expect(overrides).to be_empty
end
it "should include now-deleted overrides that weren't deleted yet as of the assignment version" do
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.save!
@assignment.due_at = 3.days.from_now
@assignment.save!
@override.destroy
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment.versions.first.model, @student)
expect(overrides).to eq [@override]
expect(overrides.first).not_to be_deleted
end
it "should include now-deleted overrides that weren't deleted yet as of the assignment version (with manage_courses permission)" do
account_admin_user
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.save!
@assignment.due_at = 3.days.from_now
@assignment.save!
@override.destroy
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(@assignment.versions.first.model, @admin)
expect(overrides).to eq [@override]
expect(overrides.first).not_to be_deleted
end
context "overrides for an assignment for a quiz, where the overrides were created before the quiz was published" do
context "without draft states" do
it "skips versions of the override that have nil for an assignment version" do
student_in_course
expected_time = Time.zone.now
quiz = @course.quizzes.create! :title => "VDD Quiz", :quiz_type => 'assignment'
section = @course.course_sections.create! :name => "title"
@course.enroll_user(@student,
'StudentEnrollment',
:section => section,
:enrollment_state => 'active',
:allow_multiple_enrollments => true)
override = quiz.assignment_overrides.build
override.quiz_id = quiz.id
override.quiz = quiz
override.set_type = 'CourseSection'
override.set_id = section.id
override.title = "Quiz Assignment override"
override.due_at = expected_time
override.save!
quiz.publish!
override = quiz.reload.assignment.assignment_overrides.first
expect(override.versions.length).to eq 1
expect(override.versions[0].model.assignment_version).not_to be_nil
# Assert that it won't call the "<=" method on nil
expect do
overrides = AssignmentOverrideApplicator.
overrides_for_assignment_and_user(quiz.assignment, @student)
end.to_not raise_error
end
end
context "with draft states" do
it "quiz should always have an assignment for overrides" do
# with draft states quizzes always have an assignment.
student_in_course
expected_time = Time.zone.now
quiz = @course.quizzes.create! :title => "VDD Quiz", :quiz_type => 'assignment'
section = @course.course_sections.create! :name => "title"
@course.enroll_user(@student,
'StudentEnrollment',
:section => section,
:enrollment_state => 'active',
:allow_multiple_enrollments => true)
override = quiz.assignment_overrides.build
override.quiz_id = quiz.id
override.quiz = quiz
override.set_type = 'CourseSection'
override.set_id = section.id
override.title = "Quiz Assignment override"
override.due_at = expected_time
override.save!
quiz.publish!
override = quiz.reload.assignment.assignment_overrides.first
expect(override.versions.length).to eq 1
expect(override.versions[0].model.assignment_version).not_to be_nil
# Assert that it won't call the "<=" method on nil
expect do
overrides = AssignmentOverrideApplicator.
overrides_for_assignment_and_user(quiz.assignment, @student)
end.to_not raise_error
end
end
end
end
end
describe "assignment_with_overrides" do
before :each do
Time.zone == 'Alaska'
@assignment = create_assignment(
:due_at => 5.days.from_now,
:unlock_at => 4.days.from_now,
:lock_at => 6.days.from_now,
:title => 'Some Title')
@override = assignment_override_model(:assignment => @assignment)
@override.override_due_at(7.days.from_now)
@overridden = AssignmentOverrideApplicator.assignment_with_overrides(@assignment, [@override])
end
it "should return a new assignment object" do
expect(@overridden.class).to eq @assignment.class
expect(@overridden.object_id).not_to eq @assignment.object_id
end
it "should preserve assignment id" do
expect(@overridden.id).to eq @assignment.id
end
it "should be new_record? iff the original assignment is" do
expect(@overridden).not_to be_new_record
@assignment = Assignment.new
@overridden = AssignmentOverrideApplicator.assignment_with_overrides(@assignment, [])
expect(@overridden).to be_new_record
end
it "should apply overrides to the returned assignment object" do
expect(@overridden.due_at).to eq @override.due_at
end
it "should not change the original assignment object" do
expect(@assignment.due_at).not_to eq @overridden.due_at
end
it "should inherit other values from the original assignment object" do
expect(@overridden.title).to eq @assignment.title
end
it "should return a readonly assignment object" do
expect(@overridden).to be_readonly
expect{ @overridden.save!(validate: false) }.to raise_exception ActiveRecord::ReadOnlyRecord
end
it "should cast datetimes to the active time zone" do
expect(@overridden.due_at.time_zone).to eq Time.zone
expect(@overridden.unlock_at.time_zone).to eq Time.zone
expect(@overridden.lock_at.time_zone).to eq Time.zone
end
it "should not cast dates to zoned datetimes" do
expect(@overridden.all_day_date.class).to eq Date
end
it "should copy pre-loaded associations" do
expect(@overridden.association(:context).loaded?).to eq @assignment.association(:context).loaded?
expect(@overridden.association(:rubric).loaded?).to eq @assignment.association(:rubric).loaded?
@overridden.learning_outcome_alignments.loaded? == @assignment.learning_outcome_alignments.loaded?
end
it "should be locked in between overrides" do
past_override = assignment_override_model(assignment: @assignment,
unlock_at: 2.months.ago,
lock_at: 1.month.ago)
future_override = assignment_override_model(assignment: @assignment,
unlock_at: 2.months.from_now,
lock_at: 1.month.from_now)
overridden = AssignmentOverrideApplicator.assignment_with_overrides(@assignment, [past_override, future_override])
expect(overridden.locked_for?(@student)).to be_truthy
end
it "should not be locked when in an override" do
override = assignment_override_model(assignment: @assignment,
unlock_at: 2.months.ago,
lock_at: 2.months.from_now)
overridden = AssignmentOverrideApplicator.assignment_with_overrides(@assignment, [override])
expect(overridden.locked_for?(@student)).to be(false)
end
end
describe "collapsed_overrides" do
it "should cache by assignment and overrides" do
@assignment = create_assignment
@override = assignment_override_model(:assignment => @assignment)
enable_cache do
overrides1 = AssignmentOverrideApplicator.collapsed_overrides(@assignment, [@override])
expect(Rails.cache).to receive(:write_entry).never
Timecop.freeze(5.seconds.from_now) do
overrides2 = AssignmentOverrideApplicator.collapsed_overrides(@assignment, [@override])
end
end
end
it "should distinguish cache by assignment" do
@assignment1 = create_assignment
@assignment2 = create_assignment
@override = assignment_override_model(:assignment => @assignment1)
enable_cache do
AssignmentOverrideApplicator.collapsed_overrides(@assignment1, [@override])
expect(Rails.cache).to receive(:write_entry)
AssignmentOverrideApplicator.collapsed_overrides(@assignment2, [@override])
end
end
it "should distinguish cache by assignment updated_at" do
@assignment = create_assignment
Timecop.travel Time.now + 1.hour do
@assignment.due_at = 5.days.from_now
@assignment.save!
expect(@assignment.versions.count).to eq 2
@override = assignment_override_model(:assignment => @assignment)
enable_cache do
expect(@assignment.versions.first.updated_at).not_to eq @assignment.versions.current.model.updated_at
AssignmentOverrideApplicator.collapsed_overrides(@assignment.versions.first.model, [@override])
expect(Rails.cache).to receive(:write_entry)
AssignmentOverrideApplicator.collapsed_overrides(@assignment.versions.current.model, [@override])
end
end
end
it "should distinguish cache by overrides" do
@assignment = create_assignment
@override1 = assignment_override_model(:assignment => @assignment)
@override2 = assignment_override_model(:assignment => @assignment)
enable_cache do
AssignmentOverrideApplicator.collapsed_overrides(@assignment, [@override1])
expect(Rails.cache).to receive(:write_entry)
AssignmentOverrideApplicator.collapsed_overrides(@assignment, [@override2])
end
end
it "should have a collapsed value for each recognized field" do
@assignment = create_assignment
@override = assignment_override_model(:assignment => @assignment)
overrides = AssignmentOverrideApplicator.collapsed_overrides(@assignment, [@override])
expect(overrides.class).to eq Hash
expect(overrides.keys.to_set).to eq [:due_at, :all_day, :all_day_date, :unlock_at, :lock_at].to_set
end
it "should use raw UTC time for datetime fields" do
Time.zone = 'Alaska'
@assignment = create_assignment(due_at: 5.days.from_now, unlock_at: 4.days.from_now, lock_at: 7.days.from_now)
collapsed = AssignmentOverrideApplicator.collapsed_overrides(@assignment, [])
expect(collapsed[:due_at].class).to eq Time
expect(collapsed[:due_at]).to eq @assignment.due_at.utc
expect(collapsed[:unlock_at].class).to eq Time
expect(collapsed[:unlock_at]).to eq @assignment.unlock_at.utc
expect(collapsed[:lock_at].class).to eq Time
expect(collapsed[:lock_at]).to eq @assignment.lock_at.utc
end
it "should not use raw UTC time for date fields" do
Time.zone = 'Alaska'
@assignment = create_assignment(:due_at => 5.days.from_now)
collapsed = AssignmentOverrideApplicator.collapsed_overrides(@assignment, [])
expect(collapsed[:all_day_date].class).to eq Date
expect(collapsed[:all_day_date]).to eq @assignment.all_day_date
end
end
describe "overrides_hash" do
it "should be consistent for the same overrides" do
overrides = 5.times.map{ assignment_override_model }
hash1 = AssignmentOverrideApplicator.overrides_hash(overrides)
hash2 = AssignmentOverrideApplicator.overrides_hash(overrides)
expect(hash1).to eq hash2
end
it "should be unique for different overrides" do
overrides1 = 5.times.map{ assignment_override_model }
overrides2 = 5.times.map{ assignment_override_model }
hash1 = AssignmentOverrideApplicator.overrides_hash(overrides1)
hash2 = AssignmentOverrideApplicator.overrides_hash(overrides2)
expect(hash1).not_to eq hash2
end
it "should be unique for different versions of the same overrides" do
overrides = 5.times.map{ assignment_override_model }
hash1 = AssignmentOverrideApplicator.overrides_hash(overrides)
overrides.first.override_due_at(5.days.from_now)
overrides.first.save!
hash2 = AssignmentOverrideApplicator.overrides_hash(overrides)
expect(hash1).not_to eq hash2
end
it "should be unique for different orders of the same overrides" do
overrides = 5.times.map{ assignment_override_model }
hash1 = AssignmentOverrideApplicator.overrides_hash(overrides)
hash2 = AssignmentOverrideApplicator.overrides_hash(overrides.reverse)
expect(hash1).not_to eq hash2
end
end
def fancy_midnight(opts={})
zone = opts[:zone] || Time.zone
Time.use_zone(zone) do
time = opts[:time] || Time.zone.now
time.in_time_zone.midnight + 1.day - 1.minute
end
end
describe "overridden_due_at" do
before :each do
@assignment = create_assignment(:due_at => 5.days.from_now)
@override = assignment_override_model(:assignment => @assignment)
end
it "should use overrides that override due_at" do
@override.override_due_at(7.days.from_now)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override])
expect(due_at).to eq @override.due_at
end
it "should skip overrides that don't override due_at" do
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_due_at(7.days.from_now)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override, @override2])
expect(due_at).to eq @override2.due_at
end
it "should prefer most lenient override" do
@override.override_due_at(6.days.from_now)
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_due_at(7.days.from_now)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override, @override2])
expect(due_at).to eq @override2.due_at
end
it "should consider no due date as most lenient" do
@override.override_due_at(nil)
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_due_at(7.days.from_now)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override, @override2])
expect(due_at).to eq @override.due_at
end
it "should not consider empty original due date as more lenient than an override due date" do
@assignment.due_at = nil
@override.override_due_at(6.days.from_now)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override])
expect(due_at).to eq @override.due_at
end
it "prefers overrides even when earlier when determining most lenient due date" do
earlier = 6.days.from_now
@assignment.due_at = 7.days.from_now
@override.override_due_at(earlier)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override])
expect(due_at).to eq earlier
end
it "should fallback on the assignment's due_at" do
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override])
expect(due_at).to eq @assignment.due_at
end
it "should recognize overrides with overridden-but-nil due_at" do
@override.override_due_at(nil)
due_at = AssignmentOverrideApplicator.overridden_due_at(@assignment, [@override])
expect(due_at).to eq @override.due_at
end
end
# specs for overridden_due_at cover all_day and all_day_date, since they're
# pulled from the same assignment/override the due_at is
describe "overridden_unlock_at" do
before :each do
@assignment = create_assignment(:due_at => 11.days.from_now, :unlock_at => 10.days.from_now)
@override = assignment_override_model(:assignment => @assignment)
end
it "should use overrides that override unlock_at" do
@override.override_unlock_at(7.days.from_now)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override])
expect(unlock_at).to eq @override.unlock_at
end
it "should skip overrides that don't override unlock_at" do
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_unlock_at(7.days.from_now)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override, @override2])
expect(unlock_at).to eq @override2.unlock_at
end
it "should prefer most lenient override" do
@override.override_unlock_at(7.days.from_now)
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_unlock_at(6.days.from_now)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override, @override2])
expect(unlock_at).to eq @override2.unlock_at
end
it "should consider no unlock date as most lenient" do
@override.override_unlock_at(nil)
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_unlock_at(7.days.from_now)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override, @override2])
expect(unlock_at).to eq @override.unlock_at
end
it "should not consider empty original unlock date as more lenient than an override unlock date" do
@assignment.unlock_at = nil
@override.override_unlock_at(6.days.from_now)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override])
expect(unlock_at).to eq @override.unlock_at
end
it "prefers overrides even when later when determining most lenient unlock date" do
later = 7.days.from_now
@assignment.unlock_at = 6.days.from_now
@override.override_unlock_at(later)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override])
expect(unlock_at).to eq later
end
it "should fallback on the assignment's unlock_at" do
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override])
expect(unlock_at).to eq @assignment.unlock_at
end
it "should recognize overrides with overridden-but-nil unlock_at" do
@override.override_unlock_at(nil)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override])
expect(unlock_at).to eq @override.unlock_at
end
it "should not include unlock_at for previous overrides that have already been locked" do
@override.override_unlock_at(10.days.ago)
@override.override_lock_at(5.days.ago)
unlock_at = AssignmentOverrideApplicator.overridden_unlock_at(@assignment, [@override])
expect(unlock_at).to eq @assignment.unlock_at
end
end
describe "overridden_lock_at" do
before :each do
@assignment = create_assignment(:due_at => 1.day.from_now, :lock_at => 5.days.from_now)
@override = assignment_override_model(:assignment => @assignment)
end
it "should use overrides that override lock_at" do
@override.override_lock_at(7.days.from_now)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override])
expect(lock_at).to eq @override.lock_at
end
it "should skip overrides that don't override lock_at" do
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_lock_at(7.days.from_now)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override, @override2])
expect(lock_at).to eq @override2.lock_at
end
it "should prefer most lenient override" do
@override.override_lock_at(6.days.from_now)
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_lock_at(7.days.from_now)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override, @override2])
expect(lock_at).to eq @override2.lock_at
end
it "should consider no lock date as most lenient" do
@override.override_lock_at(nil)
@override2 = assignment_override_model(:assignment => @assignment)
@override2.override_lock_at(7.days.from_now)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override, @override2])
expect(lock_at).to eq @override.lock_at
end
it "should not consider empty original lock date as more lenient than an override lock date" do
@assignment.lock_at = nil
@override.override_lock_at(6.days.from_now)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override])
expect(lock_at).to eq @override.lock_at
end
it "prefers overrides even when earlier when determining most lenient lock date" do
earlier = 6.days.from_now
@assignment.lock_at = 7.days.from_now
@override.override_lock_at(earlier)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override])
expect(lock_at).to eq earlier
end
it "should fallback on the assignment's lock_at" do
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override])
expect(lock_at).to eq @assignment.lock_at
end
it "should recognize overrides with overridden-but-nil lock_at" do
@override.override_lock_at(nil)
lock_at = AssignmentOverrideApplicator.overridden_lock_at(@assignment, [@override])
expect(lock_at).to eq @override.lock_at
end
end
describe "Overridable#has_no_overrides" do
before do
student_in_course
@assignment = create_assignment(:course => @course,
:due_at => 1.week.from_now)
o = assignment_override_model(:assignment => @assignment,
:due_at => 1.week.ago)
o.assignment_override_students.create! user: @student
end
it "makes assignment_overridden_for lie!" do
truly_overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
@assignment.has_no_overrides = true
fake_overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(fake_overridden_assignment.overridden).to be_truthy
expect(fake_overridden_assignment.due_at).not_to eq truly_overridden_assignment.due_at
expect(fake_overridden_assignment.due_at).to eq @assignment.due_at
end
end
describe "without_overrides" do
before :each do
student_in_course
@assignment = create_assignment(:course => @course)
end
it "should return an unoverridden copy of an overridden assignment" do
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(@overridden_assignment.overridden_for_user.id).to eq @student.id
@unoverridden_assignment = @overridden_assignment.without_overrides
expect(@unoverridden_assignment.overridden_for_user).to eq nil
end
end
it "should use the full stack" do
student_in_course
original_due_at = 3.days.from_now
@assignment = create_assignment(:course => @course)
@assignment.due_at = original_due_at
@assignment.save!
@assignment.reload
@section_override = assignment_override_model(:assignment => @assignment)
@section_override.set = @course.default_section
@section_override.override_due_at(5.days.from_now)
@section_override.save!
@section_override.reload
@adhoc_override = assignment_override_model(:assignment => @assignment)
@override_student = @adhoc_override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
@adhoc_override.override_due_at(7.days.from_now)
@adhoc_override.save!
@adhoc_override.reload
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(@overridden_assignment.due_at).to eq @adhoc_override.due_at
@adhoc_override.clear_due_at_override
@adhoc_override.save!
@overridden_assignment = AssignmentOverrideApplicator.assignment_overridden_for(@assignment, @student)
expect(@overridden_assignment.due_at).to eq @section_override.due_at
end
it "should not cache incorrect overrides through due_between_with_overrides" do
course_with_student(:active_all => true)
@assignment = create_assignment(:course => @course, :submission_types => "online_upload")
so = assignment_override_model(:assignment => @assignment)
so.set = @course.default_section
so.override_due_at(30.days.from_now) # set it outside of the default upcoming events range
so.save!
other_so = assignment_override_model(:assignment => @assignment)
other_so.set = @course.course_sections.create!
other_so.override_due_at(5.days.from_now) # set it so it would be included in the upcoming events query
other_so.save!
Timecop.freeze(5.seconds.from_now) do
enable_cache do
@student.upcoming_events # prime the cache
@assignment.reload
expect(@assignment.overridden_for(@student).due_at).to eq so.due_at # should have cached correctly
end
end
end
end