canvas-lms/spec/models/enrollment_spec.rb

1295 lines
48 KiB
Ruby

#
# Copyright (C) 2011 - 2012 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 File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe Enrollment do
before(:each) do
@user = User.create!
@course = Course.create!
@enrollment = Enrollment.new(valid_enrollment_attributes)
end
it "should be valid" do
@enrollment.should be_valid
end
it "should have an interesting state machine" do
enrollment_model
list = {}
list.stubs(:find_all_by_context_id_and_context_type).returns([])
@user.stubs(:dashboard_messages).returns(list)
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.state.should eql(:active)
@enrollment.reject
@enrollment.state.should eql(:rejected)
enrollment_model
@enrollment.complete
@enrollment.state.should eql(:completed)
enrollment_model
@enrollment.reject
@enrollment.state.should eql(:rejected)
enrollment_model
@enrollment.accept
@enrollment.state.should eql(:active)
end
it "should find students" do
@student_list = mock('student list')
@student_list.stubs(:map).returns(['student list'])
Enrollment.expects(:find).returns(@student_list)
Enrollment.students.should eql(['student list'])
end
it "should be pending if it is invited or creation_pending" do
enrollment_model(:workflow_state => 'invited')
@enrollment.should be_pending
enrollment_model(:workflow_state => 'creation_pending')
@enrollment.should be_pending
end
it "should have a context_id as the course_id" do
@enrollment.course.id.should_not be_nil
@enrollment.context_id.should eql(@enrollment.course.id)
end
it "should have a readable_type of Teacher for a TeacherEnrollment" do
e = TeacherEnrollment.new
e.type = 'TeacherEnrollment'
e.readable_type.should eql('Teacher')
end
it "should have a readable_type of Student for a StudentEnrollment" do
e = StudentEnrollment.new
e.type = 'StudentEnrollment'
e.readable_type.should eql('Student')
end
it "should have a readable_type of TaEnrollment for a TA" do
e = TaEnrollment.new(valid_enrollment_attributes)
e.type = 'TaEnrollment'
e.readable_type.should eql('TA')
end
it "should have a defalt readable_type of Student" do
e = Enrollment.new
e.type = 'Other'
e.readable_type.should eql('Student')
end
it "should not allow read permission on a course if date inactive" do
course_with_student(:active_all => true)
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
@course.grants_right?(@enrollment.user, nil, :read).should eql(false)
# post to forum comes from role_override; inactive enrollments should not
# get any permissions form role_override
@course.grants_right?(@enrollment.user, nil, :post_to_forum).should eql(false)
end
it "should not allow read permission on a course if explicitly inactive" do
course_with_student(:active_all => true)
@enrollment.workflow_state = 'inactive'
@enrollment.save!
@course.grants_right?(@enrollment.user, nil, :read).should eql(false)
@course.grants_right?(@enrollment.user, nil, :post_to_forum).should eql(false)
end
it "should allow read, but not post_to_forum on a course if date completed" do
course_with_student(:active_all => true)
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.workflow_state = 'active'
@enrollment.save!
@course.grants_right?(@enrollment.user, nil, :read).should eql(true)
# post to forum comes from role_override; completed enrollments should not
# get any permissions form role_override
@course.grants_right?(@enrollment.user, nil, :post_to_forum).should eql(false)
end
it "should allow read, but not post_to_forum on a course if explicitly completed" do
course_with_student(:active_all => true)
@enrollment.workflow_state = 'completed'
@enrollment.save!
@course.grants_right?(@enrollment.user, nil, :read).should eql(true)
@course.grants_right?(@enrollment.user, nil, :post_to_forum).should eql(false)
end
context "typed_enrollment" do
it "should allow StudentEnrollment" do
Enrollment.typed_enrollment('StudentEnrollment').should eql(StudentEnrollment)
end
it "should allow TeacherEnrollment" do
Enrollment.typed_enrollment('TeacherEnrollment').should eql(TeacherEnrollment)
end
it "should allow TaEnrollment" do
Enrollment.typed_enrollment('TaEnrollment').should eql(TaEnrollment)
end
it "should allow ObserverEnrollment" do
Enrollment.typed_enrollment('ObserverEnrollment').should eql(ObserverEnrollment)
end
it "should allow DesignerEnrollment" do
Enrollment.typed_enrollment('DesignerEnrollment').should eql(DesignerEnrollment)
end
it "should allow not NothingEnrollment" do
Enrollment.typed_enrollment('NothingEnrollment').should eql(nil)
end
end
context "drop scores" do
before(:each) do
course_with_student
@group = @course.assignment_groups.create!(:name => "some group", :group_weight => 50, :rules => "drop_lowest:1")
@assignment = @group.assignments.build(:title => "some assignments", :points_possible => 10)
@assignment.context = @course
@assignment.save!
@assignment2 = @group.assignments.build(:title => "some assignment 2", :points_possible => 40)
@assignment2.context = @course
@assignment2.save!
end
it "should drop high scores for groups when specified" do
@group.update_attribute(:rules, "drop_highest:1")
@user.enrollments.first.computed_current_score.should eql(nil)
@submission = @assignment.grade_student(@user, :grade => "9")
@submission[0].score.should eql(9.0)
@user.enrollments.should_not be_empty
@user.enrollments.first.computed_current_score.should eql(90.0)
@submission2 = @assignment2.grade_student(@user, :grade => "20")
@submission2[0].score.should eql(20.0)
@user.reload
@user.enrollments.first.computed_current_score.should eql(50.0)
@group.reload
@group.rules = nil
@group.save
@user.reload
@user.enrollments.first.computed_current_score.should eql(58.0)
end
it "should drop low scores for groups when specified" do
@user.enrollments.first.computed_current_score.should eql(nil)
@submission = @assignment.grade_student(@user, :grade => "9")
@submission2 = @assignment2.grade_student(@user, :grade => "20")
@submission2[0].score.should eql(20.0)
@user.reload
@user.enrollments.first.computed_current_score.should eql(90.0)
@group.update_attribute(:rules, "")
@user.reload
@user.enrollments.first.computed_current_score.should eql(58.0)
end
it "should not drop the last score for a group, even if the settings say it should be dropped" do
@group.update_attribute(:rules, "drop_lowest:2")
@user.enrollments.first.computed_current_score.should eql(nil)
@submission = @assignment.grade_student(@user, :grade => "9")
@submission[0].score.should eql(9.0)
@user.enrollments.should_not be_empty
@user.enrollments.first.computed_current_score.should eql(90.0)
@submission2 = @assignment2.grade_student(@user, :grade => "20")
@submission2[0].score.should eql(20.0)
@user.reload
@user.enrollments.first.computed_current_score.should eql(90.0)
end
end
context "notifications" do
it "should send out invitations if the course is already published" do
Notification.create!(:name => "Enrollment Registration")
course_with_teacher(:active_all => true)
user_with_pseudonym
e = @course.enroll_student(@user)
e.messages_sent.should be_include("Enrollment Registration")
end
it "should not send out invitations if the course is not yet published" do
Notification.create!(:name => "Enrollment Registration")
course_with_teacher
user_with_pseudonym
e = @course.enroll_student(@user)
e.messages_sent.should_not be_include("Enrollment Registration")
end
it "should send out invitations for previously-created enrollments when the course is published" do
n = Notification.create(:name => "Enrollment Registration", :category => "Registration")
course_with_teacher
user_with_pseudonym
e = @course.enroll_student(@user)
e.messages_sent.should_not be_include("Enrollment Registration")
@user.pseudonym.should_not be_nil
@course.offer
e.reload
e.should be_invited
e.user.should_not be_nil
e.user.pseudonym.should_not be_nil
Message.last.should_not be_nil
Message.last.notification.should eql(n)
Message.last.to.should eql(@user.email)
end
end
context "atom" do
it "should use the course and user name to derive a title" do
@enrollment.to_atom.title.should eql("#{@enrollment.user.name} in #{@enrollment.course.name}")
end
it "should link to the enrollment" do
link_path = @enrollment.to_atom.links.first.to_s
link_path.should eql("/courses/#{@enrollment.course.id}/enrollments/#{@enrollment.id}")
end
end
context "permissions" do
it "should be able to read grades if the course grants management rights to the enrollment" do
@new_user = user_model
@enrollment.grants_rights?(@new_user, nil, :read_grades)[:read_grades].should be_false
@course.instructors << @new_user
@course.save!
@enrollment.grants_rights?(@user, nil, :read_grades).should be_true
end
it "should allow the user itself to read its own grades" do
@enrollment.grants_rights?(@user, nil, :read_grades).should be_true
end
end
context "recompute_final_score_if_stale" do
it "should only call recompute_final_score once within the cache window" do
course_with_student
Enrollment.expects(:recompute_final_score).once
enable_cache do
Enrollment.recompute_final_score_if_stale @course
Enrollment.recompute_final_score_if_stale @course
end
end
it "should yield iff it calls recompute_final_score" do
course_with_student
Enrollment.expects(:recompute_final_score).once
count = 1
enable_cache do
Enrollment.recompute_final_score_if_stale(@course, @user){ count += 1 }
Enrollment.recompute_final_score_if_stale(@course, @user){ count += 1 }
end
count.should eql 2
end
end
context "recompute_final_scores" do
it "should only recompute once per student, per course" do
course_with_student(:active_all => true)
@c1 = @course
@s2 = @course.course_sections.create!(:name => 's2')
@course.enroll_student(@user, :section => @s2, :allow_multiple_enrollments => true)
@user.student_enrollments(true).count.should == 2
course_with_student(:user => @user)
@c2 = @course
Enrollment.recompute_final_scores(@user.id)
jobs = Delayed::Job.all(:conditions => { :tag => 'Enrollment.recompute_final_score' })
jobs.size.should == 2
# pull the course ids out of the job params
jobs.map { |j| j.payload_object.args[1] }.sort.should == [@c1.id, @c2.id]
end
end
context "date restrictions" do
context "accept" do
it "should accept into the right state based on availability dates on enrollment" do
course_with_student(:active_all => true)
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:invited)
@enrollment.accept
@enrollment.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:completed)
@enrollment.accept.should be_false
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:inactive)
@enrollment.accept.should be_false
end
it "should accept into the right state based on availability dates on course_section" do
course_with_student(:active_all => true)
@section = @course.course_sections.first
@section.should_not be_nil
@enrollment.course_section = @section
@enrollment.workflow_state = 'invited'
@enrollment.save!
@section.start_at = 2.days.ago
@section.end_at = 2.days.from_now
@section.restrict_enrollments_to_section_dates = true
@section.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
@section.start_at = 4.days.ago
@section.end_at = 2.days.ago
@section.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:completed)
@enrollment.accept.should be_false
@section.start_at = 2.days.from_now
@section.end_at = 4.days.from_now
@section.save!
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:inactive)
@enrollment.accept.should be_false
end
it "should accept into the right state based on availability dates on course" do
course_with_student(:active_all => true)
@course.start_at = 2.days.ago
@course.conclude_at = 2.days.from_now
@course.restrict_enrollments_to_course_dates = true
@course.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.reload
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:inactive)
@enrollment.accept.should be_false
end
it "should accept into the right state based on availability dates on enrollment_term" do
course_with_student(:active_all => true)
@term = @course.enrollment_term
@term.should_not be_nil
@term.start_at = 2.days.ago
@term.end_at = 2.days.from_now
@term.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.reload
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:inactive)
@enrollment.accept.should be_false
end
it "should accept into the right state based on availability dates on enrollment_dates_override" do
course_with_student(:active_all => true)
@term = @course.enrollment_term
@term.should_not be_nil
@term.save!
@override = @term.enrollment_dates_overrides.create!(:enrollment_type => 'StudentEnrollment', :enrollment_term => @term)
@override.start_at = 2.days.ago
@override.end_at = 2.days.from_now
@override.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
@override.start_at = 4.days.ago
@override.end_at = 2.days.ago
@override.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.state.should eql(:invited)
@enrollment.accept
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
@override.start_at = 2.days.from_now
@override.end_at = 4.days.from_now
@override.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.reload
@enrollment.state.should eql(:invited)
@enrollment.state_based_on_date.should eql(:inactive)
@enrollment.accept.should be_false
@enrollment.update_attribute(:workflow_state, 'active')
@override.start_at = nil
@override.end_at = nil
@override.save!
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.save!
@enrollment.reload.state_based_on_date.should eql(:inactive)
end
end
context 'dates change' do
before(:all) do
@old_cache = RAILS_CACHE
silence_warnings { Object.const_set(:RAILS_CACHE, ActiveSupport::Cache::MemoryStore.new) }
end
after(:all) do
silence_warnings { Object.const_set(:RAILS_CACHE, @old_cache) }
end
it "should return the right state based on availability dates on enrollment" do
course_with_student(:active_all => true)
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
@enrollment.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
sleep 1
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
@enrollment.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
sleep 1
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
@enrollment.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:inactive)
end
it "should return the right state based on availability dates on course_section" do
course_with_student(:active_all => true)
@section = @course.course_sections.first
@section.should_not be_nil
@enrollment.course_section = @section
@enrollment.workflow_state = 'active'
@enrollment.save!
@section.start_at = 2.days.ago
@section.end_at = 2.days.from_now
@section.restrict_enrollments_to_section_dates = true
@section.save!
@enrollment.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
sleep 1
@section.start_at = 4.days.ago
@section.end_at = 2.days.ago
@section.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
sleep 1
@section.start_at = 2.days.from_now
@section.end_at = 4.days.from_now
@section.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:inactive)
end
it "should return the right state based on availability dates on course" do
course_with_student(:active_all => true)
@course.start_at = 2.days.ago
@course.conclude_at = 2.days.from_now
@course.restrict_enrollments_to_course_dates = true
@course.save!
@enrollment.workflow_state = 'active'
@enrollment.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
sleep 1
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
sleep 1
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:inactive)
end
it "should return the right state based on availability dates on enrollment_term" do
course_with_student(:active_all => true)
@term = @course.enrollment_term
@term.should_not be_nil
@term.start_at = 2.days.ago
@term.end_at = 2.days.from_now
@term.save!
@enrollment.workflow_state = 'active'
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
sleep 1
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.reset_touched_courses_flag
@term.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
sleep 1
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.reset_touched_courses_flag
@term.save!
@enrollment.course.reload
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:inactive)
end
it "should return the right state based on availability dates on enrollment_dates_override" do
course_with_student(:active_all => true)
@term = @course.enrollment_term
@term.should_not be_nil
@term.save!
@override = @term.enrollment_dates_overrides.create!(:enrollment_type => 'StudentEnrollment', :enrollment_term => @term)
@override.start_at = 2.days.ago
@override.end_at = 2.days.from_now
@override.save!
@enrollment.workflow_state = 'active'
@enrollment.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:active)
sleep 1
@override.start_at = 4.days.ago
@override.end_at = 2.days.ago
@term.reset_touched_courses_flag
@override.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:completed)
sleep 1
@override.start_at = 2.days.from_now
@override.end_at = 4.days.from_now
@term.reset_touched_courses_flag
@override.save!
@enrollment.reload.state.should eql(:active)
@enrollment.state_based_on_date.should eql(:inactive)
end
end
it "should allow teacher access if both course and term have dates" do
@teacher_enrollment = course_with_teacher(:active_all => 1)
@student_enrollment = student_in_course(:active_all => 1)
@term = @course.enrollment_term
@teacher_enrollment.state.should == :active
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state.should == :active
@student_enrollment.state_based_on_date.should == :active
# Course dates completely before Term dates, now in course dates
@course.start_at = 2.days.ago
@course.conclude_at = 2.days.from_now
@course.restrict_enrollments_to_course_dates = true
@course.save!
@term.start_at = 4.days.from_now
@term.end_at = 6.days.from_now
@term.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :active
# Term dates completely before Course dates, now in course dates
@term.start_at = 6.days.ago
@term.end_at = 4.days.ago
@term.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :active
# Terms dates superset of course dates, now in both
@term.start_at = 4.days.ago
@term.end_at = 4.days.from_now
@term.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :active
# Course dates superset of term dates, now in both
@course.start_at = 6.days.ago
@course.conclude_at = 6.days.from_now
@course.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :active
# Course dates superset of term dates, now in beginning non-overlap
@term.start_at = 2.days.from_now
@term.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :active
# Course dates superset of term dates, now in ending non-overlap
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :active
# Term dates superset of course dates, now in beginning non-overlap
@term.start_at = 6.days.ago
@term.end_at = 6.days.from_now
@term.save!
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :inactive
# Term dates superset of course dates, now in ending non-overlap
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :completed
# Course dates completely before term dates, now in term dates
@course.start_at = 6.days.ago
@course.conclude_at = 4.days.ago
@course.save!
@term.start_at = 2.days.ago
@term.end_at = 2.days.from_now
@term.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :completed
# Course dates completely after term dates, now in term dates
@course.start_at = 4.days.from_now
@course.conclude_at = 6.days.from_now
@course.save!
@teacher_enrollment.state_based_on_date.should == :active
@student_enrollment.state_based_on_date.should == :inactive
# Now between course and term dates, term first
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.save!
@teacher_enrollment.state_based_on_date.should == :completed
@student_enrollment.state_based_on_date.should == :inactive
# Now after both dates
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
@teacher_enrollment.state_based_on_date.should == :completed
@student_enrollment.state_based_on_date.should == :completed
# Now before both dates
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.save!
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.save!
@teacher_enrollment.state_based_on_date.should == :inactive
@student_enrollment.state_based_on_date.should == :inactive
# Now between course and term dates, course first
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
@teacher_enrollment.state_based_on_date.should == :completed
@student_enrollment.state_based_on_date.should == :completed
end
it "should affect the active?/inactive?/completed? predicates" do
course_with_student(:active_all => true)
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
@enrollment.active?.should be_true
@enrollment.inactive?.should be_false
@enrollment.completed?.should be_false
sleep 1
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
@enrollment.active?.should be_false
@enrollment.inactive?.should be_false
@enrollment.completed?.should be_true
sleep 1
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
@enrollment.active?.should be_false
@enrollment.inactive?.should be_true
@enrollment.completed?.should be_false
end
it "should not affect the explicitly_completed? predicate" do
course_with_student(:active_all => true)
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
@enrollment.explicitly_completed?.should be_false
sleep 1
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
@enrollment.explicitly_completed?.should be_false
sleep 1
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
@enrollment.explicitly_completed?.should be_false
@enrollment.workflow_state = 'completed'
@enrollment.explicitly_completed?.should be_true
end
it "should affect the completed_at" do
yesterday = 1.day.ago
course_with_student(:active_all => true)
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.completed_at = nil
@enrollment.save!
@enrollment.completed_at.should be_nil
@enrollment.completed_at = yesterday
@enrollment.completed_at.should == yesterday
sleep 1
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.completed_at = nil
@enrollment.save!
@enrollment.completed_at.should == @enrollment.end_at
@enrollment.completed_at = yesterday
@enrollment.completed_at.should == yesterday
end
end
context "audit_groups_for_deleted_enrollments" do
it "should ungroup the user when the enrollment is deleted" do
# set up course with two users in one section
course_with_teacher(:active_all => true)
user1 = user_model
user2 = user_model
section1 = @course.course_sections.create
section1.enroll_user(user1, 'StudentEnrollment')
section1.enroll_user(user2, 'StudentEnrollment')
# set up a group without a group category and put both users in it
group = @course.groups.create
group.add_user(user1)
group.add_user(user2)
# remove user2 from the section (effectively unenrolled from the course)
user2.enrollments.first.destroy
group.reload
# he should be removed from the group
group.users.size.should == 1
group.users.should_not be_include(user2)
group.should have_common_section
end
it "should ungroup the user when a changed enrollment causes conflict" do
# set up course with two users in one section
course_with_teacher(:active_all => true)
user1 = user_model
user2 = user_model
section1 = @course.course_sections.create
section1.enroll_user(user1, 'StudentEnrollment')
section1.enroll_user(user2, 'StudentEnrollment')
# set up a group category in that course with restricted self sign-up and
# put both users in one of its groups
category = @course.group_categories.build
category.configure_self_signup(true, true)
category.save
group = category.groups.create(:context => @course)
group.add_user(user1)
group.add_user(user2)
category.should_not have_heterogenous_group
# move a user to a new section
section2 = @course.course_sections.create
enrollment = user2.enrollments.first
enrollment.course_section = section2
enrollment.save
group.reload
category.reload
# he should be removed from the group, keeping the group and the category
# happily satisfying the self sign-up restriction.
group.users.size.should == 1
group.users.should_not be_include(user2)
group.should have_common_section
category.should_not have_heterogenous_group
end
it "should not ungroup the user when a the group doesn't care" do
# set up course with two users in one section
course_with_teacher(:active_all => true)
user1 = user_model
user2 = user_model
section1 = @course.course_sections.create
section1.enroll_user(user1, 'StudentEnrollment')
section1.enroll_user(user2, 'StudentEnrollment')
# set up a group category in that course *without* restrictions on self
# sign-up and put both users in one of its groups
category = @course.group_categories.build
category.configure_self_signup(true, false)
category.save
group = category.groups.create(:context => @course)
group.add_user(user1)
group.add_user(user2)
# move a user to a new section
section2 = @course.course_sections.create
enrollment = user2.enrollments.first
enrollment.course_section = section2
enrollment.save
group.reload
category.reload
# he should still be in the group
group.users.size.should == 2
group.users.should be_include(user2)
end
it "should ungroup the user even when there's not another user in the group if the enrollment is deleted" do
# set up course with only one user in one section
course_with_teacher(:active_all => true)
user1 = user_model
section1 = @course.course_sections.create
section1.enroll_user(user1, 'StudentEnrollment')
# set up a group category in that course with restricted self sign-up and
# put the user in one of its groups
category = @course.group_categories.build
category.configure_self_signup(true, false)
category.save
group = category.groups.create(:context => @course)
group.add_user(user1)
# remove the user from the section (effectively unenrolled from the course)
user1.enrollments.first.destroy
group.reload
category.reload
# he should not be in the group
group.users.size.should == 0
end
it "should not ungroup the user when there's not another user in the group" do
# set up course with only one user in one section
course_with_teacher(:active_all => true)
user1 = user_model
section1 = @course.course_sections.create
section1.enroll_user(user1, 'StudentEnrollment')
# set up a group category in that course with restricted self sign-up and
# put the user in one of its groups
category = @course.group_categories.build
category.configure_self_signup(true, false)
category.save
group = category.groups.create(:context => @course)
group.add_user(user1)
# move a user to a new section
section2 = @course.course_sections.create
enrollment = user1.enrollments.first
enrollment.course_section = section2
enrollment.save
group.reload
category.reload
# he should still be in the group
group.users.size.should == 1
group.users.should be_include(user1)
end
it "should ignore previously deleted memberships" do
# set up course with a user in one section
course_with_teacher(:active_all => true)
user = user_model
section1 = @course.course_sections.create
enrollment = section1.enroll_user(user, 'StudentEnrollment')
# set up a group without a group category and put the user in it
group = @course.groups.create
group.add_user(user)
# mark the membership as deleted
membership = group.group_memberships.find_by_user_id(user.id)
membership.workflow_state = 'deleted'
membership.save!
# delete the enrollment to trigger audit_groups_for_deleted_enrollments processing
lambda {enrollment.destroy}.should_not raise_error
# she should still be removed from the group
group.users.size.should == 0
group.users.should_not be_include(user)
end
end
describe "for_email" do
it "should return candidate enrollments" do
course(:active_all => 1)
user
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@course.enroll_user(@user)
Enrollment.invited.for_email('jt@instructure.com').count.should == 1
end
it "should not return non-candidate enrollments" do
course(:active_all => 1)
# mismatched e-mail
user
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'bob@instructure.com')
@course.enroll_user(@user)
# registered user
user
@user.communication_channels.create!(:path => 'jt@instructure.com')
@user.register!
@course.enroll_user(@user)
# active e-mail
user
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com') { |cc| cc.workflow_state = 'active' }
@course.enroll_user(@user)
# accepted enrollment
user
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@course.enroll_user(@user).accept
# rejected enrollment
user
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@course.enroll_user(@user).reject
Enrollment.invited.for_email('jt@instructure.com').should == []
end
end
it "should uncache temporary user invitations when state changes" do
enable_cache do
course(:active_all => 1)
user
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@enrollment = @course.enroll_user(@user)
Enrollment.cached_temporary_invitations('jt@instructure.com').length.should == 1
@enrollment.accept
Enrollment.cached_temporary_invitations('jt@instructure.com').should == []
end
end
it "should uncache user enrollments when rejected" do
enable_cache do
course_with_student(:active_course => 1)
User.update_all({:updated_at => 1.year.ago}, :id => @user.id)
@user.reload
@user.cached_current_enrollments.should == [@enrollment]
@enrollment.reject!
@user.cached_current_enrollments(true).should == []
end
end
context "named scopes" do
describe "ended" do
it "should work" do
course(:active_all => 1)
user
Enrollment.ended.should == []
@enrollment = Enrollment.create!(:user => @user, :course => @course)
Enrollment.ended.should == []
@enrollment.update_attribute(:workflow_state, 'active')
Enrollment.ended.should == []
@enrollment.update_attribute(:workflow_state, 'completed')
Enrollment.ended.should == [@enrollment]
@enrollment.update_attribute(:workflow_state, 'rejected')
Enrollment.ended.should == [@enrollment]
end
end
end
describe "destroy" do
it "should update user_account_associations" do
course_with_teacher(:active_all => 1)
@user.associated_accounts.should == [Account.default]
@enrollment.destroy
@user.associated_accounts(true).should == []
end
end
describe ".remove_duplicate_enrollments_from_sections" do
before do
course_with_student(:active_all => true)
@e1 = @enrollment
@e1.sis_batch_id = 2
@e1.sis_source_id = 'ohai'
@e1.save!
end
it "should leave single enrollments alone" do
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(0)
@e1.reload.should be_active
end
it "should remove duplicates" do
enrollment_model(:course_section => @course.course_sections.first, :user => @user, :sis_source_id => 'ohai', :workflow_state => 'active', :type => "StudentEnrollment")
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(-1)
end
it "should prefer the highest sis_batch_id" do
enrollment_model(:course_section => @course.course_sections.first, :user => @user, :sis_source_id => 'ohai', :type => "StudentEnrollment", :sis_batch_id => 1)
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(-1)
@e1.reload.state.should == :active
end
it "should group by user_id" do
enrollment_model(:course_section => @course.course_sections.first, :user => user, :sis_source_id => 'ohai2', :type => "StudentEnrollment")
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(0)
end
it "should group by type" do
enrollment_model(:course_section => @course.course_sections.first, :user => @user, :sis_source_id => 'ohai', :type => "TeacherEnrollment")
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(0)
end
it "should group by section" do
enrollment_model(:course_section => @course.course_sections.create!(:name => 's2'), :user => @user, :sis_source_id => 'ohai', :type => "StudentEnrollment")
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(0)
end
it "should group by associated_user_id" do
enrollment_model(:course_section => @course.course_sections.first, :user => @user, :sis_source_id => 'ohai', :associated_user_id => user.id, :type => "StudentEnrollment")
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(0)
end
it "should ignore non-sis enrollments" do
@e1.update_attribute('sis_source_id', nil)
enrollment_model(:course_section => @course.course_sections.first, :user => @user, :type => "StudentEnrollment")
expect { Enrollment.remove_duplicate_enrollments_from_sections }.to change(Enrollment, :count).by(0)
end
end
describe "effective_start_at" do
before :each do
course_with_student(:active_all => true)
(@term = @course.enrollment_term).should_not be_nil
(@section = @enrollment.course_section).should_not be_nil
# 7 different possible times, make sure they're distinct
@enrollment_date_start_at = 7.days.ago
@enrollment.start_at = 6.days.ago
@section.start_at = 5.days.ago
@course.start_at = 4.days.ago
@term.start_at = 3.days.ago
@section.created_at = 2.days.ago
@course.created_at = 1.days.ago
end
it "should utilize to enrollment_dates if it has a value" do
@enrollment.stubs(:enrollment_dates).returns([[@enrollment_date_start_at, nil]])
@enrollment.effective_start_at.should == @enrollment_date_start_at
end
it "should use earliest value from enrollment_dates if it has multiple" do
@enrollment.stubs(:enrollment_dates).returns([[@enrollment.start_at, nil], [@enrollment_date_start_at, nil]])
@enrollment.effective_start_at.should == @enrollment_date_start_at
end
it "should follow chain of fallbacks in correct order if no enrollment_dates" do
@enrollment.stubs(:enrollment_dates).returns([[nil, Time.now]])
# start peeling away things from most preferred to least preferred to
# test fallback chain
@enrollment.effective_start_at.should == @enrollment.start_at
@enrollment.start_at = nil
@enrollment.effective_start_at.should == @section.start_at
@section.start_at = nil
@enrollment.effective_start_at.should == @course.start_at
@course.start_at = nil
@enrollment.effective_start_at.should == @term.start_at
@term.start_at = nil
@enrollment.effective_start_at.should == @section.created_at
@section.created_at = nil
@enrollment.effective_start_at.should == @course.created_at
@course.created_at = nil
@enrollment.effective_start_at.should be_nil
end
it "should not explode when missing section or term" do
@enrollment.course_section = nil
@course.enrollment_term = nil
@enrollment.effective_start_at.should == @enrollment.start_at
@enrollment.start_at = nil
@enrollment.effective_start_at.should == @course.start_at
@course.start_at = nil
@enrollment.effective_start_at.should == @course.created_at
@course.created_at = nil
@enrollment.effective_start_at.should be_nil
end
end
describe "effective_end_at" do
before :each do
course_with_student(:active_all => true)
(@term = @course.enrollment_term).should_not be_nil
(@section = @enrollment.course_section).should_not be_nil
# 5 different possible times, make sure they're distinct
@enrollment_date_end_at = 1.days.ago
@enrollment.end_at = 2.days.ago
@section.end_at = 3.days.ago
@course.conclude_at = 4.days.ago
@term.end_at = 5.days.ago
end
it "should utilize to enrollment_dates if it has a value" do
@enrollment.stubs(:enrollment_dates).returns([[nil, @enrollment_date_end_at]])
@enrollment.effective_end_at.should == @enrollment_date_end_at
end
it "should use earliest value from enrollment_dates if it has multiple" do
@enrollment.stubs(:enrollment_dates).returns([[nil, @enrollment.end_at], [nil, @enrollment_date_end_at]])
@enrollment.effective_end_at.should == @enrollment_date_end_at
end
it "should follow chain of fallbacks in correct order if no enrollment_dates" do
@enrollment.stubs(:enrollment_dates).returns([[nil, nil]])
# start peeling away things from most preferred to least preferred to
# test fallback chain
@enrollment.effective_end_at.should == @enrollment.end_at
@enrollment.end_at = nil
@enrollment.effective_end_at.should == @section.end_at
@section.end_at = nil
@enrollment.effective_end_at.should == @course.conclude_at
@course.conclude_at = nil
@enrollment.effective_end_at.should == @term.end_at
@term.end_at = nil
@enrollment.effective_end_at.should be_nil
end
it "should not explode when missing section or term" do
@enrollment.course_section = nil
@course.enrollment_term = nil
@enrollment.effective_end_at.should == @enrollment.end_at
@enrollment.end_at = nil
@enrollment.effective_end_at.should == @course.conclude_at
@course.conclude_at = nil
@enrollment.effective_end_at.should be_nil
end
end
describe 'conclude' do
it "should remove the enrollment from User#cached_current_enrollments" do
enable_cache do
course_with_student(:active_all => 1)
User.update_all({:updated_at => 1.day.ago}, :id => @user.id)
@user.reload
@user.cached_current_enrollments.should == [ @enrollment ]
@enrollment.conclude
@user.reload
@user.cached_current_enrollments(true).should == []
end
end
end
end