canvas-lms/spec/models/enrollment_spec.rb

2549 lines
97 KiB
Ruby

#
# 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/>.
#
require_relative '../sharding_spec_helper'
describe Enrollment do
before(:once) do
@user = User.create!
@course = Course.create!
@enrollment = StudentEnrollment.new(valid_enrollment_attributes)
end
it "should be valid" do
expect(@enrollment).to be_valid
end
it "should have an interesting state machine" do
enrollment_model
@user.stubs(:dashboard_messages).returns(Message.none)
expect(@enrollment.state).to eql(:invited)
@enrollment.accept
expect(@enrollment.state).to eql(:active)
@enrollment.reject
expect(@enrollment.state).to eql(:rejected)
Score.where(enrollment_id: @enrollment).delete_all
@enrollment.destroy_permanently!
enrollment_model
@enrollment.complete
expect(@enrollment.state).to eql(:completed)
@enrollment.destroy_permanently!
enrollment_model
@enrollment.reject
expect(@enrollment.state).to eql(:rejected)
@enrollment.destroy_permanently!
enrollment_model
@enrollment.accept
expect(@enrollment.state).to eql(:active)
end
it "should be pending if it is invited or creation_pending" do
enrollment_model(:workflow_state => 'invited')
expect(@enrollment).to be_pending
@enrollment.destroy_permanently!
enrollment_model(:workflow_state => 'creation_pending')
expect(@enrollment).to be_pending
end
it "should have a context_id as the course_id" do
expect(@enrollment.course.id).not_to be_nil
expect(@enrollment.context_id).to eql(@enrollment.course.id)
end
it "should have a readable_type of Teacher for a TeacherEnrollment" do
e = TeacherEnrollment.new
e.type = 'TeacherEnrollment'
expect(e.readable_type).to eql('Teacher')
end
it "should have a readable_type of Student for a StudentEnrollment" do
e = StudentEnrollment.new
e.type = 'StudentEnrollment'
expect(e.readable_type).to eql('Student')
end
it "should have a readable_type of TaEnrollment for a TA" do
e = TaEnrollment.new(valid_enrollment_attributes)
e.type = 'TaEnrollment'
expect(e.readable_type).to eql('TA')
end
it "should have a defalt readable_type of Student" do
e = Enrollment.new
e.type = 'Other'
expect(e.readable_type).to eql('Student')
end
describe "sis_role" do
it "should return role_name if present" do
role = custom_account_role('Assistant Grader', :account => Account.default)
e = TaEnrollment.new
e.role_id = role.id
expect(e.sis_role).to eq 'Assistant Grader'
end
it "should return the sis enrollment type otherwise" do
e = TaEnrollment.new
expect(e.sis_role).to eq 'ta'
end
end
describe '#destroy' do
before(:once) do
@enrollment = StudentEnrollment.create!(valid_enrollment_attributes)
assignment = @course.assignments.create!
@override = assignment.assignment_overrides.create!
@override.assignment_override_students.create!(user: @enrollment.user)
end
let(:override_student) { @override.assignment_override_students.find_by(user_id: @enrollment.user) }
it 'does not destroy assignment override students on the user if other enrollments' \
'for the user exist in the course' do
@course.enroll_user(
@enrollment.user,
'StudentEnrollment',
section: @course.course_sections.create!,
allow_multiple_enrollments: true
)
@enrollment.destroy
expect(override_student).to be_present
end
it 'destroys assignment override students on the user if no other enrollments for the user exist in the course' do
@enrollment.destroy
expect(override_student).not_to be_present
end
end
describe '#restore' do
before(:once) do
@enrollment.save!
@enrollment.scores.create!
@enrollment.destroy
end
it 'restores associated scores that are deleted' do
expect { @enrollment.restore }.to change {
Score.find_by(enrollment_id: @enrollment, grading_period_id: nil).workflow_state
}.from('deleted').to('active')
end
it 'does not restore scores associated with other enrollments' do
new_enrollment = StudentEnrollment.create!(user: User.create!, course: @course)
new_score = new_enrollment.scores.new
new_score.workflow_state = :deleted
new_score.save!
expect { @enrollment.restore }.not_to change { new_score.reload.workflow_state }
end
end
describe 'scores and grades' do
let(:new_student_enrollment) do
@course.enroll_user(
@enrollment.user,
'StudentEnrollment',
section: @course.course_sections.create!,
allow_multiple_enrollments: true
)
end
let(:new_fake_student_enrollment) do
@course.enroll_user(
@enrollment.user,
'StudentViewEnrollment',
section: @course.course_sections.create!,
allow_multiple_enrollments: true
)
end
describe 'current scores and grades' do
before(:once) do
@enrollment = StudentEnrollment.create!(valid_enrollment_attributes)
end
let(:period) do
group = @course.root_account.grading_period_groups.create!
group.grading_periods.create!(
title: 'period',
start_date: 'Jan 1, 2015',
end_date: 'Jan 5, 2015'
)
end
describe '#computed_current_score' do
it 'uses the value from the associated score object, if one exists' do
@enrollment.scores.create!(current_score: 80.3)
expect(@enrollment.computed_current_score).to eq 80.3
end
it 'uses the value from the associated score object, even if it is nil' do
@enrollment.scores.create!(current_score: nil)
expect(@enrollment.computed_current_score).to be_nil
end
it 'ignores grading period scores when passed no arguments' do
@enrollment.scores.create!(current_score: 80.3, grading_period: period)
expect(@enrollment.computed_current_score).to be_nil
end
it 'ignores soft-deleted scores' do
score = @enrollment.scores.create!(current_score: 80.3)
score.destroy
expect(@enrollment.computed_current_score).to be_nil
end
it 'computes current score for a given grading period id' do
@enrollment.scores.create!(current_score: 80.3)
@enrollment.scores.create!(current_score: 70.6, grading_period: period)
current_score = @enrollment.computed_current_score(grading_period_id: period.id)
expect(current_score).to eq 70.6
end
it 'returns nil if a grading period score is requested and does not exist' do
current_score = @enrollment.computed_current_score(grading_period_id: period.id)
expect(current_score).to be_nil
end
end
describe '#computed_current_grade' do
before(:each) do
@course.grading_standard_enabled = true
@course.save!
end
it 'uses the value from the associated score object, if one exists' do
@enrollment.scores.create!(current_score: 80.3)
expect(@enrollment.computed_current_grade).to eq 'B-'
end
it 'ignores grading period grades when passed no arguments' do
@enrollment.scores.create!(current_score: 80.3, grading_period: period)
expect(@enrollment.computed_current_grade).to be_nil
end
it 'ignores grades from soft-deleted scores' do
score = @enrollment.scores.create!(current_score: 80.3)
score.destroy
expect(@enrollment.computed_current_grade).to be_nil
end
it 'computes current grade for a given grading period id' do
@enrollment.scores.create!(current_score: 70.6, grading_period: period)
current_grade = @enrollment.computed_current_grade(grading_period_id: period.id)
expect(current_grade).to eq 'C-'
end
it 'returns nil if a grading period grade is requested and does not exist' do
current_grade = @enrollment.computed_current_grade(grading_period_id: period.id)
expect(current_grade).to be_nil
end
end
describe '#graded_at' do
it 'uses the updated_at from the associated score object, if one exists' do
score = @enrollment.scores.create!(current_score: 80.3)
score.update_attribute(:updated_at, 5.days.from_now)
expect(@enrollment.graded_at).to eq score.updated_at
end
it 'uses the graded_at attribute if no associated score object exists' do
expect(@enrollment.graded_at).to eq @enrollment.read_attribute(:graded_at)
end
it 'ignores grading period scores' do
@enrollment.scores.create!(current_score: 80.3, grading_period: period)
expect(@enrollment.graded_at).to eq @enrollment.read_attribute(:graded_at)
end
it 'ignores soft-deleted scores' do
score = @enrollment.scores.create!(current_score: 80.3)
score.destroy
expect(@enrollment.graded_at).to eq @enrollment.read_attribute(:graded_at)
end
end
describe 'copying current scores' do
before(:once) do
teacher = User.create!
@course.enroll_teacher(teacher, active_all: true)
assignment = @course.assignments.create!(points_possible: 100)
assignment.grade_student(@enrollment.user, grade: 95, grader: teacher)
end
context 'on creation' do
it 'copies scores over from existing student enrollments to new student enrollments' do
expect(new_student_enrollment.computed_current_score).to eq(@enrollment.computed_current_score)
end
it 'copies scores over from existing fake student enrollments to new fake student enrollments' do
@enrollment.update!(type: 'StudentViewEnrollment')
expect(new_fake_student_enrollment.computed_current_score).to eq(@enrollment.computed_current_score)
end
it 'does not copy scores if the new enrollment type does not match existing enrollment types' do
expect(new_fake_student_enrollment.computed_current_score).to be_nil
end
it 'does not copy scores if the existing enrollment is soft-deleted' do
@enrollment.destroy
expect(new_student_enrollment.computed_current_score).to be_nil
end
end
context 'on restoration' do
it 'copies scores over from existing student enrollments to restored student enrollments' do
new_student_enrollment.destroy
new_student_enrollment.update!(workflow_state: :active)
expect(new_student_enrollment.computed_current_score).to eq(@enrollment.computed_current_score)
end
it 'copies scores over from existing fake student enrollments to restored fake student enrollments' do
@enrollment.update!(type: 'StudentViewEnrollment')
new_fake_student_enrollment.destroy
new_fake_student_enrollment.update!(workflow_state: :active)
expect(new_fake_student_enrollment.computed_current_score).to eq(@enrollment.computed_current_score)
end
it 'does not copy scores if the restored enrollment type does not match existing enrollment types' do
new_fake_student_enrollment.destroy
new_fake_student_enrollment.update!(workflow_state: :active)
expect(new_fake_student_enrollment.computed_current_score).to be_nil
end
it 'does not copy scores if the existing enrollment is soft-deleted' do
@enrollment.destroy
new_student_enrollment.destroy
new_student_enrollment.update!(workflow_state: :active)
expect(new_student_enrollment.computed_current_score).to be_nil
end
end
end
end
describe 'final scores and grades' do
before(:once) do
@enrollment.save!
end
let(:period) do
group = @course.root_account.grading_period_groups.create!
group.grading_periods.create!(
title: 'period',
start_date: 'Jan 1, 2015',
end_date: 'Jan 5, 2015'
)
end
describe '#find_score' do
it 'returns the course score when no arg is passed' do
score = @enrollment.scores.create!(final_score: 80.3)
@enrollment.scores.create!(final_score: 80.3, grading_period_id: period.id)
expect(@enrollment.find_score).to eq score
end
it 'returns the grading period score when grading_period_id is passed' do
@enrollment.scores.create!(final_score: 80.3)
score = @enrollment.scores.create!(final_score: 80.3, grading_period_id: period.id)
expect(@enrollment.find_score(grading_period_id: period.id)).to eq score
end
end
describe '#computed_final_score' do
it 'uses the value from the associated score object, if one exists' do
@enrollment.scores.create!(final_score: 80.3)
expect(@enrollment.computed_final_score).to eq 80.3
end
it 'uses the value from the associated score object, even if it is nil' do
@enrollment.scores.create!(final_score: nil)
expect(@enrollment.computed_final_score).to eq nil
end
it 'ignores grading period scores when passed no arguments' do
@enrollment.scores.create!(final_score: 80.3, grading_period: period)
expect(@enrollment.computed_final_score).to be_nil
end
it 'ignores soft-deleted scores' do
score = @enrollment.scores.create!(final_score: 80.3)
score.destroy
expect(@enrollment.computed_final_score).to be_nil
end
it 'computes final score for a given grading period id' do
@enrollment.scores.create!(final_score: 70.6, grading_period: period)
final_score = @enrollment.computed_final_score(grading_period_id: period.id)
expect(final_score).to eq 70.6
end
it 'returns nil if a grading period score is requested and does not exist' do
final_score = @enrollment.computed_final_score(grading_period_id: period.id)
expect(final_score).to eq nil
end
end
describe '#computed_final_grade' do
before(:each) do
@course.grading_standard_enabled = true
@course.save!
end
it 'uses the value from the associated score object, if one exists' do
@enrollment.scores.create!(final_score: 80.3)
expect(@enrollment.computed_final_grade).to eq 'B-'
end
it 'ignores grading period grades when passed no arguments' do
@enrollment.scores.create!(final_score: 80.3, grading_period: period)
expect(@enrollment.computed_final_grade).to be_nil
end
it 'ignores grades from soft-deleted scores' do
score = @enrollment.scores.create!(final_score: 80.3)
score.destroy
expect(@enrollment.computed_final_grade).to be_nil
end
it 'computes final grade for a given grading period id' do
@enrollment.scores.create!(final_score: 80.3)
@enrollment.scores.create!(final_score: 70.6, grading_period: period)
final_grade = @enrollment.computed_final_grade(grading_period_id: period.id)
expect(final_grade).to eq 'C-'
end
it 'returns nil if a grading period grade is requested and does not exist' do
final_grade = @enrollment.computed_final_grade(grading_period_id: period.id)
expect(final_grade).to eq nil
end
end
context 'copying final scores on creation' do
before(:once) do
teacher = User.create!
@course.enroll_teacher(teacher, active_all: true)
assignment = @course.assignments.create!(points_possible: 100)
assignment.grade_student(@enrollment.user, grade: 95, grader: teacher)
# create this so the enrollment's current_score differs from its final_score
@course.assignments.create!(points_possible: 100)
end
it 'copies scores over from the user\'s existing student enrollments to new student enrollments' do
expect(new_student_enrollment.computed_final_score).to eq(@enrollment.computed_final_score)
end
it 'copies scores over from the user\'s existing fake student enrollments to new fake student enrollments' do
@enrollment.update!(type: 'StudentViewEnrollment')
expect(new_fake_student_enrollment.computed_final_score).to eq(@enrollment.computed_final_score)
end
it 'does not copy scores if the new enrollment type does not match existing enrollment types' do
expect(new_fake_student_enrollment.computed_final_score).to be_nil
end
it 'does not copy scores if the existing enrollment is soft-deleted' do
@enrollment.destroy
expect(new_student_enrollment.computed_final_score).to be_nil
end
end
end
end
it "should not allow an associated_user_id on a non-observer enrollment" do
observed = User.create!
@enrollment.type = 'ObserverEnrollment'
@enrollment.associated_user_id = observed.id
expect(@enrollment).to be_valid
@enrollment.type = 'StudentEnrollment'
expect(@enrollment).not_to be_valid
@enrollment.associated_user_id = nil
expect(@enrollment).to be_valid
end
it "should not allow an associated_user_id = user_id" do
observed = User.create!
@enrollment.type = 'ObserverEnrollment'
@enrollment.user_id = observed.id
@enrollment.associated_user_id = observed.id
expect(@enrollment).to_not be_valid
end
context "permissions" do
before(:once) do
course_with_student(:active_all => true)
end
it "should allow post_to_forum permission on a course if date is current" do
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 4.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@enrollment.reload.state_based_on_date).to eq :active
expect(@course.grants_right?(@enrollment.user, :post_to_forum)).to eql(true)
end
it "should not allow post_to_forum permission on a course if date in future" do
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@enrollment.reload.state_based_on_date).to eq :accepted
expect(@course.grants_right?(@enrollment.user, :post_to_forum)).to eql(false)
end
it "should not allow read permission on a course if date inactive" do
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
@course.restrict_student_future_view = true
@course.save!
expect(@course.grants_right?(@enrollment.user, :read)).to eql(false)
# post to forum comes from role_override; inactive enrollments should not
# get any permissions form role_override
expect(@course.grants_right?(@enrollment.user, :post_to_forum)).to eql(false)
end
it "should not allow read permission on a course if explicitly inactive" do
@enrollment.workflow_state = 'inactive'
@enrollment.save!
expect(@course.grants_right?(@enrollment.user, :read)).to eql(false)
expect(@course.grants_right?(@enrollment.user, :post_to_forum)).to eql(false)
end
it "should allow read, but not post_to_forum on a course if date completed" do
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@course.grants_right?(@enrollment.user, :read)).to eql(true)
# post to forum comes from role_override; completed enrollments should not
# get any permissions form role_override
expect(@course.grants_right?(@enrollment.user, :post_to_forum)).to eql(false)
end
it "should allow read, but not post_to_forum on a course if explicitly completed" do
@enrollment.workflow_state = 'completed'
@enrollment.save!
expect(@course.grants_right?(@enrollment.user, :read)).to eql(true)
expect(@course.grants_right?(@enrollment.user, :post_to_forum)).to eql(false)
end
end
context "typed_enrollment" do
it "should allow StudentEnrollment" do
expect(Enrollment.typed_enrollment('StudentEnrollment')).to eql(StudentEnrollment)
end
it "should allow TeacherEnrollment" do
expect(Enrollment.typed_enrollment('TeacherEnrollment')).to eql(TeacherEnrollment)
end
it "should allow TaEnrollment" do
expect(Enrollment.typed_enrollment('TaEnrollment')).to eql(TaEnrollment)
end
it "should allow ObserverEnrollment" do
expect(Enrollment.typed_enrollment('ObserverEnrollment')).to eql(ObserverEnrollment)
end
it "should allow DesignerEnrollment" do
expect(Enrollment.typed_enrollment('DesignerEnrollment')).to eql(DesignerEnrollment)
end
it "should allow not NothingEnrollment" do
expect(Enrollment.typed_enrollment('NothingEnrollment')).to eql(nil)
end
end
context "drop scores" do
before(:once) do
course_with_teacher
course_with_student(course: @course)
@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
@enrollment = @user.enrollments.first
@group.update_attribute(:rules, "drop_highest:1")
expect(@enrollment.reload.computed_current_score).to eql(nil)
@submission = @assignment.grade_student(@user, grade: "9", grader: @teacher)
expect(@submission[0].score).to eql(9.0)
expect(@enrollment.reload.computed_current_score).to eql(90.0)
@submission2 = @assignment2.grade_student(@user, grade: "20", grader: @teacher)
expect(@submission2[0].score).to eql(20.0)
expect(@enrollment.reload.computed_current_score).to eql(50.0)
@group.update_attribute(:rules, nil)
expect(@enrollment.reload.computed_current_score).to eql(58.0)
end
it "should drop low scores for groups when specified" do
@enrollment = @user.enrollments.first
expect(@enrollment.reload.computed_current_score).to eql(nil)
@submission = @assignment.grade_student(@user, grade: "9", grader: @teacher)
@submission2 = @assignment2.grade_student(@user, grade: "20", grader: @teacher)
expect(@submission2[0].score).to eql(20.0)
expect(@enrollment.reload.computed_current_score).to eql(90.0)
@group.update_attribute(:rules, "")
expect(@enrollment.reload.computed_current_score).to eql(58.0)
end
it "should not drop the last score for a group, even if the settings say it should be dropped" do
@enrollment = @user.enrollments.first
@group.update_attribute(:rules, "drop_lowest:2")
expect(@enrollment.reload.computed_current_score).to eql(nil)
@submission = @assignment.grade_student(@user, grade: "9", grader: @teacher)
expect(@submission[0].score).to eql(9.0)
expect(@enrollment.reload.computed_current_score).to eql(90.0)
@submission2 = @assignment2.grade_student(@user, grade: "20", grader: @teacher)
expect(@submission2[0].score).to eql(20.0)
expect(@enrollment.reload.computed_current_score).to 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)
expect(e.messages_sent).to be_include("Enrollment Registration")
end
it "should not send out invitations immediately if the course restricts future viewing" do
Notification.create!(:name => "Enrollment Registration")
course_with_teacher(:active_all => true)
@course.restrict_student_future_view = true
@course.restrict_enrollments_to_course_dates = true
@course.start_at = 1.day.from_now
@course.conclude_at = 3.days.from_now
@course.save!
user_with_pseudonym
e = @course.enroll_student(@user)
expect(e).to be_inactive
expect(e.messages_sent).to_not include("Enrollment Registration")
Timecop.freeze(2.days.from_now) do
expect(e).to be_invited
e.any_instantiation.expects(:re_send_confirmation!).once
run_jobs
end
end
it "should not send out invitations to an observer if the student doesn't receive an invitation (e.g. sis import)" do
Notification.create!(:name => "Enrollment Registration", :category => "Registration")
course_with_teacher(:active_all => true)
student = user_with_pseudonym
observer = user_with_pseudonym
observer.observed_users << student
@course.enroll_student(student, :no_notify => true)
expect(student.messages).to be_empty
expect(observer.messages).to be_empty
course_with_teacher(:active_all => true)
@course.enroll_student(student)
student.reload
observer.reload
expect(student.messages).to_not be_empty
expect(observer.messages).to be_empty
end
it "should not send out invitations to an observer if the course is not published" do
Notification.create!(:name => "Enrollment Registration", :category => "Registration")
course_with_teacher
student = user_with_pseudonym
observer = user_with_pseudonym
observer.observed_users << student
@course.enroll_student(student)
expect(observer.messages).to be_empty
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)
expect(e.messages_sent).not_to 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)
expect(e.messages_sent).not_to be_include("Enrollment Registration")
expect(@user.pseudonym).not_to be_nil
@course.offer
e.reload
expect(e).to be_invited
expect(e.user).not_to be_nil
expect(e.user.pseudonym).not_to be_nil
expect(Message.last).not_to be_nil
expect(Message.last.notification).to eql(n)
expect(Message.last.to).to eql(@user.email)
end
it "should send out notifications for enrollment acceptance correctly" do
teacher = user_with_pseudonym(:active_all => true)
n = Notification.create!(:name => "Enrollment Accepted")
NotificationPolicy.create!(:notification => n, :communication_channel => @user.communication_channel, :frequency => "immediately")
course_with_teacher(:active_all => true, :user => teacher)
student = user_factory
e = @course.enroll_student(student)
e.accept!
expect(teacher.messages).to be_exists
end
it "should not send out notifications for enrollment acceptance to admins who are section restricted and in other sections" do
# even though section restrictions are still basically meaningless at this point
teacher = user_with_pseudonym(:active_all => true)
n = Notification.create!(:name => "Enrollment Accepted")
NotificationPolicy.create!(:notification => n, :communication_channel => @user.communication_channel, :frequency => "immediately")
course_with_teacher(:active_all => true, :user => teacher)
teacher.enrollments.first.update_attribute(:limit_privileges_to_course_section, true)
other_section = @course.course_sections.create!
e1 = @course.enroll_student(user_factory, :section => other_section)
e1.accept!
expect(teacher.messages).to_not be_exists
e2 = @course.enroll_student(user_factory, :section => @course.default_section)
e2.accept!
expect(teacher.messages).to be_exists
end
end
context "atom" do
it "should use the course and user name to derive a title" do
expect(@enrollment.to_atom.title).to 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
expect(link_path).to eql("/courses/#{@enrollment.course.id}/enrollments/#{@enrollment.id}")
end
end
context "permissions" do
it "should grant read rights to account members with the ability to read_roster" do
role = Role.get_built_in_role("AccountMembership")
user = account_admin_user(:role => role)
RoleOverride.create!(:context => Account.default, :permission => :read_roster,
:role => role, :enabled => true)
@enrollment.save
expect(@enrollment.user.grants_right?(user, :read)).to eq false
expect(@enrollment.grants_right?(user, :read)).to eq true
end
it "should be able to read grades if the course grants management rights to the enrollment" do
@new_user = user_model
@enrollment.save
expect(@enrollment.grants_right?(@new_user, :read_grades)).to be_falsey
@course.enroll_teacher(@new_user)
@enrollment.reload
AdheresToPolicy::Cache.clear
expect(@enrollment.grants_right?(@user, :read_grades)).to be_truthy
end
it "should allow the user itself to read its own grades" do
expect(@enrollment.grants_right?(@user, :read_grades)).to be_truthy
end
end
context "recompute_final_score_if_stale" do
before(:once) { course_with_student }
it "should only call recompute_final_score once within the cache window" do
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
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
expect(count).to eql 2
end
end
context "recompute_final_score_in_singleton" do
before(:once) { course_with_student }
it "should raise an exception if called with more than one user" do
expect { Enrollment.recompute_final_score_in_singleton([@user.id, 5], @course.id) }.
to raise_error(ArgumentError)
end
it "sends later for a single student" do
expect(Enrollment).to receive(:send_later_if_production_enqueue_args).with(
:recompute_final_score,
hash_including(singleton: "Enrollment.recompute_final_score:#{@user.id}:#{@course.id}:"),
@user.id, @course.id, {}
)
Enrollment.recompute_final_score_in_singleton(@user.id, @course.id)
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)
expect(@user.student_enrollments.reload.count).to eq 2
course_with_student(:user => @user)
@c2 = @course
Enrollment.expects(:recompute_final_score).with(@user.id, @c1.id, {})
Enrollment.expects(:recompute_final_score).with(@user.id, @c2.id, {})
Enrollment.recompute_final_scores(@user.id)
end
end
context "date restrictions" do
context "accept" do
def enrollment_availability_test
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:invited)
@enrollment.accept
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.reload.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:completed)
expect(@enrollment.accept).to be_falsey
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
expect(@enrollment.reload.state).to eql(:invited)
if @enrollment.admin?
expect(@enrollment.state_based_on_date).to eq(:inactive)
else
expect(@enrollment.state_based_on_date).to eql(:invited)
expect(@enrollment.accept).to be_truthy
end
end
def course_section_availability_test(should_be_invited=false)
@section = @course.course_sections.first
expect(@section).not_to 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!
expect(@enrollment.state).to eql(:invited)
@enrollment.accept
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
@section.start_at = 4.days.ago
@section.end_at = 2.days.ago
@section.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.reload.state).to eql(:invited)
if should_be_invited
expect(@enrollment.state_based_on_date).to eql(:invited)
expect(@enrollment.accept).to be_truthy
else
expect(@enrollment.state_based_on_date).to eql(:completed)
expect(@enrollment.accept).to be_falsey
end
@section.start_at = 2.days.from_now
@section.end_at = 4.days.from_now
@section.save!
@enrollment.save!
@enrollment.reload
if should_be_invited
expect(@enrollment.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
else
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:invited)
expect(@enrollment.accept).to be_truthy
end
end
def course_availability_test(state_based_state)
@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!
expect(@enrollment.state).to eql(:invited)
@enrollment.accept
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.state).to eql(:invited)
@enrollment.accept if @enrollment.invited?
expect(@enrollment.state_based_on_date).to eql(state_based_state)
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.reload
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:invited)
expect(@enrollment.accept).to be_truthy
@course.complete!
expect(@enrollment.reload.state).to eql(:completed)
expect(@enrollment.state_based_on_date).to eql(:completed)
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@enrollment.state_based_on_date).to eql(:completed)
end
def enrollment_term_availability_test
@term = @course.enrollment_term
expect(@term).not_to be_nil
@term.start_at = 2.days.ago
@term.end_at = 2.days.from_now
@term.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:invited)
@enrollment.accept
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:completed)
expect(@enrollment.accept).to be_falsey
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.save!
@enrollment.reload
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to eql(:invited)
expect(@enrollment.accept).to be_truthy
expect(@enrollment.reload.state_based_on_date).to eql(@enrollment.admin? ? :active : :accepted)
end
def enrollment_dates_override_test
@term = @course.enrollment_term
expect(@term).not_to be_nil
@term.save!
@override = @term.enrollment_dates_overrides.create!(:enrollment_type => @enrollment.type, :enrollment_term => @term)
@override.start_at = 2.days.ago
@override.end_at = 2.days.from_now
@override.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.state).to eql(:invited)
@enrollment.accept
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
@override.start_at = 4.days.ago
@override.end_at = 2.days.ago
@override.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.state).to eql(:invited)
expect(@enrollment.state_based_on_date).to 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
expect(@enrollment.state).to eql(:invited)
if @enrollment.admin?
expect(@enrollment.state_based_on_date).to eql(:inactive)
else
expect(@enrollment.state_based_on_date).to eql(:invited)
expect(@enrollment.accept).to be_truthy
end
@course.restrict_student_future_view = true
@course.save!
@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!
expected = @enrollment.admin? ? :active : :inactive
expect(@enrollment.reload.state_based_on_date).to eql(expected)
end
context "as a student" do
before :once do
course_with_student(:active_all => true)
end
it "accepts into the right state based on availability dates on enrollment" do
enrollment_availability_test
end
it "accepts into the right state based on availability dates on course_section" do
course_section_availability_test
end
it "accepts into the right state based on availability dates on course" do
course_availability_test(:completed)
end
it "accepts into the right state based on availability dates on enrollment_term" do
enrollment_term_availability_test
end
it "accepts into the right state based on availability dates on enrollment_dates_override" do
enrollment_dates_override_test
end
it "has the correct state for a half-open past course" do
@term = @course.enrollment_term
expect(@term).not_to be_nil
@term.start_at = nil
@term.end_at = 2.days.ago
@term.save!
@enrollment.workflow_state = 'invited'
@enrollment.save!
expect(@enrollment.reload.state).to eq :invited
expect(@enrollment.state_based_on_date).to eq :completed
end
it "recomputes scores for the student" do
Enrollment.expects(:recompute_final_score).with(@enrollment.user_id, @enrollment.course_id, {})
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.accept
end
end
context "as a teacher" do
before :once do
course_with_teacher(:active_all => true)
end
it "accepts into the right state based on availability dates on enrollment" do
enrollment_availability_test
end
it "accepts into the right state based on availability dates on course_section" do
course_section_availability_test(true)
end
it "accepts into the right state based on availability dates on course" do
course_availability_test(:active)
end
it "accepts into the right state based on availability dates on enrollment_term" do
enrollment_term_availability_test
end
it "accepts into the right state based on availability dates on enrollment_dates_override" do
enrollment_dates_override_test
end
it "does not attempt to recompute scores since the user is not a student" do
Enrollment.expects(:recompute_final_score).never
@enrollment.workflow_state = 'invited'
@enrollment.save!
@enrollment.accept
end
end
end
shared_examples_for 'term and enrollment dates' do
describe 'enrollment dates' do
it "should return active enrollment" do
@enrollment.start_at = 2.days.ago
@enrollment.end_at = 2.days.from_now
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@enrollment.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
end
it "should return completed enrollment" do
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:completed)
end
it "should return accepted if upcoming and available" do
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(@enrollment.admin? ? :inactive : :accepted)
end
it "should return inactive for students (accepted for admins) if upcoming and not available" do
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
@course.restrict_student_future_view = true
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:inactive)
end
end
describe 'term dates' do
before do
@term = @course.enrollment_term
end
it "should return active" do
@term.start_at = 2.days.ago
@term.end_at = 2.days.from_now
@term.save!
@enrollment.workflow_state = 'active'
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
expect(Enrollment.where(:id => @enrollment).active_by_date.first).to eq @enrollment
end
it "should return completed" do
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.reset_touched_courses_flag
@term.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:completed)
expect(Enrollment.where(:id => @enrollment).active_by_date.first).to be_nil
end
it "should return accepted for students (inactive for admins) if upcoming and available" do
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.reset_touched_courses_flag
@term.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(@enrollment.admin? ? :active : :accepted)
end
it "should return inactive for all users if upcoming and not available" do
@term.start_at = 2.days.from_now
@term.end_at = 4.days.from_now
@term.reset_touched_courses_flag
@term.save!
@course.restrict_student_future_view = true
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(@enrollment.admin? ? :active : :inactive)
if @enrollment.student?
expect(Enrollment.where(:id => @enrollment).active_by_date.first).to be_nil
end
end
end
describe 'enrollment_dates_override dates' do
before do
@term = @course.enrollment_term
@override = @term.enrollment_dates_overrides.create!(:enrollment_type => @enrollment.type, :enrollment_term => @term)
end
it "should return active" do
@override.start_at = 2.days.ago
@override.end_at = 2.days.from_now
@override.save!
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
end
it "should return completed" do
@override.start_at = 4.days.ago
@override.end_at = 2.days.ago
@term.reset_touched_courses_flag
@override.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:completed)
end
it "should return accepted if upcoming and available (and inactive for admins)" do
@override.start_at = 2.days.from_now
@override.end_at = 4.days.from_now
@term.reset_touched_courses_flag
@override.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(@enrollment.admin? ? :inactive : :accepted)
end
it "should return inactive for all users if upcoming and not available" do
@override.start_at = 2.days.from_now
@override.end_at = 4.days.from_now
@term.reset_touched_courses_flag
@override.save!
@course.restrict_student_future_view = true
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:inactive)
end
end
end
context 'dates for students' do
before :once do
enable_cache
Timecop.freeze(10.minutes.ago) do
course_with_student(active_all: true)
end
end
include_examples 'term and enrollment dates'
describe 'section dates' do
before do
@section = @course.course_sections.first
@section.restrict_enrollments_to_section_dates = true
end
it "should return active" do
@section.start_at = 2.days.ago
@section.end_at = 2.days.from_now
@section.save!
expect(@enrollment.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
end
it "should return completed" do
@section.start_at = 4.days.ago
@section.end_at = 2.days.ago
@section.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:completed)
end
it "should return accepted if upcoming and available" do
@section.start_at = 2.days.from_now
@section.end_at = 4.days.from_now
@section.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:accepted)
end
it "should return inactive if upcoming and not available" do
@section.start_at = 2.days.from_now
@section.end_at = 4.days.from_now
@section.save!
@course.restrict_student_future_view = true
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:inactive)
end
end
describe 'course dates' do
before do
@course.restrict_enrollments_to_course_dates = true
end
it "should return active" do
@course.start_at = 2.days.ago
@course.conclude_at = 2.days.from_now
@course.save!
@enrollment.workflow_state = 'active'
@enrollment.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:active)
end
it "should return completed" do
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:completed)
end
it "should return accepted if upcoming and available" do
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:accepted)
end
it "should return inactive if upcoming and not available" do
@course.start_at = 2.days.from_now
@course.conclude_at = 4.days.from_now
@course.restrict_student_future_view = true
@course.save!
expect(@enrollment.reload.state).to eql(:active)
expect(@enrollment.state_based_on_date).to eql(:inactive)
end
end
end
context 'dates for teachers' do
before :once do
enable_cache
Timecop.freeze(10.minutes.ago) do
course_with_teacher(active_all: true)
end
end
include_examples 'term and enrollment dates'
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
expect(@teacher_enrollment.state).to eq :active
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :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!
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :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!
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :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!
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :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!
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :active
# Course dates superset of term dates, now in beginning non-overlap
@term.start_at = 2.days.from_now
@term.save!
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :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!
expect(@teacher_enrollment.state_based_on_date).to eq :active
expect(@student_enrollment.state_based_on_date).to eq :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!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :active
expect(@student_enrollment.reload.state_based_on_date).to eq :accepted
@course.restrict_student_future_view = true
@course.save!
expect(@student_enrollment.reload.state_based_on_date).to eq :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!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :active
expect(@student_enrollment.reload.state_based_on_date).to eq :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!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :active
expect(@student_enrollment.reload.state_based_on_date).to eq :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!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :active
expect(@student_enrollment.reload.state_based_on_date).to eq :inactive
# Now between course and term dates, term first
@term.start_at = 4.days.ago
@term.end_at = 2.days.ago
@term.save!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :completed
expect(@student_enrollment.reload.state_based_on_date).to eq :inactive
# Now after both dates
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :completed
expect(@student_enrollment.reload.state_based_on_date).to eq :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!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :active
expect(@student_enrollment.reload.state_based_on_date).to eq :inactive
# Now between course and term dates, course first
@course.start_at = 4.days.ago
@course.conclude_at = 2.days.ago
@course.save!
expect(@teacher_enrollment.reload.state_based_on_date).to eq :active
expect(@student_enrollment.reload.state_based_on_date).to eq :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!
expect(@enrollment.active?).to be_truthy
expect(@enrollment.inactive?).to be_falsey
expect(@enrollment.completed?).to be_falsey
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
@enrollment.reload
expect(@enrollment.active?).to be_falsey
expect(@enrollment.inactive?).to be_falsey
expect(@enrollment.completed?).to be_truthy
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
@enrollment.reload
expect(@enrollment.active?).to be_falsey
expect(@enrollment.completed?).to be_falsey
expect(@enrollment.accepted?).to be_truthy
@course.restrict_student_future_view = true
@course.save!
@enrollment.reload
expect(@enrollment.inactive?).to be_truthy
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!
expect(@enrollment.explicitly_completed?).to be_falsey
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
expect(@enrollment.explicitly_completed?).to be_falsey
@enrollment.start_at = 2.days.from_now
@enrollment.end_at = 4.days.from_now
@enrollment.save!
expect(@enrollment.explicitly_completed?).to be_falsey
@enrollment.workflow_state = 'completed'
expect(@enrollment.explicitly_completed?).to be_truthy
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!
expect(@enrollment.completed_at).to be_nil
@enrollment.completed_at = yesterday
expect(@enrollment.completed_at).to eq yesterday
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.completed_at = nil
@enrollment.save!
@enrollment.reload
expect(@enrollment.completed_at).to eq @enrollment.end_at
@enrollment.completed_at = yesterday
expect(@enrollment.completed_at).to eq yesterday
end
end
context "audit_groups_for_deleted_enrollments" do
before :once do
course_with_teacher(:active_all => true)
end
it "should ungroup the user when the enrollment is deleted" do
# set up course with two users in one section
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
expect(group.users.size).to eq 1
expect(group.users).not_to be_include(user2)
expect(group).to 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
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 = group_category
category.configure_self_signup(true, true)
category.save
group = category.groups.create(:context => @course)
group.add_user(user1)
group.add_user(user2)
expect(category).not_to 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.
expect(group.users.size).to eq 1
expect(group.users).not_to be_include(user2)
expect(group).to have_common_section
expect(category).not_to 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
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 = group_category
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
expect(group.users.size).to eq 2
expect(group.users).to be_include(user2)
end
it "should ungroup the user from all groups, restricted and unrestricted when completely unenrolling from the course" do
user1 = user_model :name => "Andy"
user2 = user_model :name => "Bruce"
section1 = @course.course_sections.create :name => "Section 1"
@course.enroll_user(user1, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
@course.enroll_user(user2, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
# created category for restricted groups
res_category = group_category :name => "restricted"
res_category.configure_self_signup(true, true)
res_category.save
# created category for unrestricted groups
unrestricted_category = group_category :name => "unrestricted"
unrestricted_category.configure_self_signup(true, false)
unrestricted_category.save
# Group 1 - restricted group
group1 = res_category.groups.create(:name => "Group1", :context => @course)
group1.add_user(user1)
group1.add_user(user2)
# Group 2 - unrestricted group
group2 = unrestricted_category.groups.create(:name => "Group2 (Unrestricted)", :context => @course)
group2.add_user(user1)
group2.add_user(user2)
user2.enrollments.where(:course_section_id => section1.id).first.destroy
group1.reload
group2.reload
expect(group1.users.size).to eq 1
expect(group2.users.size).to eq 1
expect(group1.users).not_to be_include(user2)
expect(group2.users).not_to be_include(user2)
expect(group1).to have_common_section
end
it "should ungroup the user from the restricted group when deleting enrollment to one section but user is still enrolled in another section" do
user1 = user_model :name => "Andy"
user2 = user_model :name => "Bruce"
section1 = @course.course_sections.create :name => "Section 1"
section2 = @course.course_sections.create :name => "Section 2"
# we should have more than one student enrolled in section to exercise common_to_section check.
@course.enroll_user(user1, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
@course.enroll_user(user2, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
# enroll user2 in a second section
@course.enroll_user(user2, 'StudentEnrollment', :section => section2, :enrollment_state => 'active', :allow_multiple_enrollments => true)
# set up a group category for restricted groups
# and put both users in one of its groups
category = group_category :name => "restricted category"
category.configure_self_signup(true, true)
category.save
# restricted group
group = category.groups.create(:name => "restricted group", :context => @course)
group.add_user(user1)
group.add_user(user2)
# remove user2 from the section (effectively unenrolled from a section of the course)
user2.enrollments.where(:course_section_id => section1.id).first.destroy
group.reload
# user2 should be removed from the group
expect(group.users.size).to eq 1
expect(group.users).not_to be_include(user2)
expect(group).to have_common_section
end
it "should not ungroup the user from unrestricted group when deleting enrollment to one section but user is still enrolled in another section" do
user1 = user_model :name => "Andy"
user2 = user_model :name => "Bruce"
section1 = @course.course_sections.create :name => "Section 1"
section2 = @course.course_sections.create :name => "Section 2"
# we should have more than one student enrolled in section to exercise common_to_section check.
@course.enroll_user(user1, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
@course.enroll_user(user2, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
# enroll user2 in a second section
@course.enroll_user(user2, 'StudentEnrollment', :section => section2, :enrollment_state => 'active', :allow_multiple_enrollments => true)
# set up a group category for unrestricted groups
unrestricted_category = group_category :name => "unrestricted category"
unrestricted_category.configure_self_signup(true, false)
unrestricted_category.save
# unrestricted group
group = unrestricted_category.groups.create(:name => "unrestricted group", :context => @course)
group.add_user(user1)
group.add_user(user2)
# remove user2 from the section (effectively unenrolled from a section of the course)
user2.enrollments.where(:course_section_id => section1.id).first.destroy
group.reload
# user2 should not be removed from group 2
expect(group.users.size).to eq 2
expect(group.users).to be_include(user2)
expect(group).not_to have_common_section
end
it "should not ungroup the user from restricted group when there's not another user in the group and user is still enrolled in another section" do
user1 = user_model :name => "Andy"
section1 = @course.course_sections.create :name => "Section 1"
section2 = @course.course_sections.create :name => "Section 2"
# enroll user in two sections
@course.enroll_user(user1, 'StudentEnrollment', :section => section1, :enrollment_state => 'active', :allow_multiple_enrollments => true)
@course.enroll_user(user1, 'StudentEnrollment', :section => section2, :enrollment_state => 'active', :allow_multiple_enrollments => true)
# set up a group category for restricted groups
restricted_category = group_category :name => "restricted category"
restricted_category.configure_self_signup(true, true)
restricted_category.save
# restricted group
group = restricted_category.groups.create(:name => "restricted group", :context => @course)
group.add_user(user1)
# remove user from the section (effectively unenrolled from a section of the course)
user1.enrollments.where(:course_section_id => section1.id).first.destroy
group.reload
# he should not be removed from the group
expect(group.users.size).to eq 1
expect(group.users).to be_include(user1)
expect(group).to have_common_section
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
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 = group_category
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
expect(group.users.size).to eq 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
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 = group_category
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
expect(group.users.size).to eq 1
expect(group.users).to be_include(user1)
end
it "should ignore previously deleted memberships" do
# set up course with a user in one section
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.where(user_id: user).first
membership.workflow_state = 'deleted'
membership.save!
# delete the enrollment to trigger audit_groups_for_deleted_enrollments processing
expect {enrollment.destroy}.not_to raise_error
# she should still be removed from the group
expect(group.users.size).to eq 0
expect(group.users).not_to be_include(user)
end
end
describe "for_email" do
before :once do
course_factory(active_all: true)
end
it "should return candidate enrollments" do
user_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@course.enroll_user(@user)
expect(Enrollment.invited.for_email('jt@instructure.com').count).to eq 1
end
it "should not return non-candidate enrollments" do
# mismatched e-mail
user_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'bob@instructure.com')
@course.enroll_user(@user)
# registered user
user_factory
@user.communication_channels.create!(:path => 'jt@instructure.com')
@user.register!
@course.enroll_user(@user)
# active e-mail
user_factory
@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_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@course.enroll_user(@user).accept
# rejected enrollment
user_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@course.enroll_user(@user).reject
expect(Enrollment.invited.for_email('jt@instructure.com')).to eq []
end
end
describe "cached_temporary_invitations" do
it "should uncache temporary user invitations when state changes" do
enable_cache do
course_factory(active_all: true)
user_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@enrollment = @course.enroll_user(@user)
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').length).to eq 1
@enrollment.accept
expect(Enrollment.cached_temporary_invitations('jt@instructure.com')).to eq []
end
end
it "should uncache user enrollments when rejected" do
enable_cache do
course_with_student(:active_course => 1)
User.where(:id => @user).update_all(:updated_at => 1.year.ago)
@user.reload
expect(@user.cached_current_enrollments).to eq [@enrollment]
@enrollment.reject!
# have to get the new updated_at
@user.reload
expect(@user.cached_current_enrollments).to eq []
end
end
it "should uncache user enrollments when deleted" do
enable_cache do
course_with_student(:active_course => 1)
User.where(:id => @user).update_all(:updated_at => 1.year.ago)
@user.reload
expect(@user.cached_current_enrollments).to eq [@enrollment]
@enrollment.destroy
# have to get the new updated_at
@user.reload
expect(@user.cached_current_enrollments).to eq []
end
end
context "sharding" do
specs_require_sharding
describe "limit_privileges_to_course_section!" do
it "should use the right shard to find the enrollments" do
@shard1.activate do
account = Account.create!
course_with_student(:active_all => true, :account => account)
end
@shard2.activate do
Enrollment.limit_privileges_to_course_section!(@course, @user, true)
end
expect(@enrollment.reload.limit_privileges_to_course_section).to be_truthy
end
end
describe "cached_temporary_invitations" do
before :once do
course_factory(active_all: true)
user_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@enrollment1 = @course.enroll_user(@user)
@shard1.activate do
account = Account.create!
course_factory(active_all: true, :account => account)
user_factory
@user.update_attribute(:workflow_state, 'creation_pending')
@user.communication_channels.create!(:path => 'jt@instructure.com')
@enrollment2 = @course.enroll_user(@user)
end
end
before :each do
Enrollment.stubs(:cross_shard_invitations?).returns(true)
skip "working CommunicationChannel.associated_shards" unless CommunicationChannel.associated_shards('jt@instructure.com').length == 2
end
it "should include invitations from other shards" do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
@shard1.activate do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
end
@shard2.activate do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
end
end
it "should have a single cache for all shards" do
enable_cache do
@shard2.activate do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
end
Shard.expects(:with_each_shard).never
@shard1.activate do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
end
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
end
end
it "should invalidate the cache from any shard" do
enable_cache do
@shard2.activate do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com').sort_by(&:global_id)).to eq [@enrollment1, @enrollment2].sort_by(&:global_id)
@enrollment2.reject!
end
@shard1.activate do
expect(Enrollment.cached_temporary_invitations('jt@instructure.com')).to eq [@enrollment1]
@enrollment1.reject!
end
expect(Enrollment.cached_temporary_invitations('jt@instructure.com')).to eq []
end
end
end
end
end
describe "#destroy" do
it "should update user_account_associations" do
course_with_teacher(:active_all => 1)
expect(@user.associated_accounts).to eq [Account.default]
@enrollment.destroy
expect(@user.associated_accounts.reload).to eq []
end
it "should remove assignment overrides if they are only linked to this enrollment" do
course_with_student
assignment = assignment_model(:course => @course)
ao = AssignmentOverride.new()
ao.assignment = assignment
ao.title = "ADHOC OVERRIDE"
ao.workflow_state = "active"
ao.set_type = "ADHOC"
ao.save!
assignment.reload
override_student = ao.assignment_override_students.build
override_student.user = @user
override_student.save!
expect(ao.workflow_state).to eq("active")
@user.enrollments.destroy_all
ao.reload
expect(ao.workflow_state).to eq("deleted")
end
it "destroys associated scores" do
@enrollment.save
score = @enrollment.scores.create!
@enrollment.destroy
expect(score.reload).to be_deleted
end
end
describe "effective_start_at" do
before :once do
course_with_student(:active_all => true)
@term = @course.enrollment_term
@section = @enrollment.course_section
# 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]])
expect(@enrollment.effective_start_at).to eq @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]])
expect(@enrollment.effective_start_at).to eq @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
expect(@enrollment.effective_start_at).to eq @enrollment.start_at
@enrollment.start_at = nil
expect(@enrollment.effective_start_at).to eq @section.start_at
@section.start_at = nil
expect(@enrollment.effective_start_at).to eq @course.start_at
@course.start_at = nil
expect(@enrollment.effective_start_at).to eq @term.start_at
@term.start_at = nil
expect(@enrollment.effective_start_at).to eq @section.created_at
@section.created_at = nil
expect(@enrollment.effective_start_at).to eq @course.created_at
@course.created_at = nil
expect(@enrollment.effective_start_at).to be_nil
end
it "should not explode when missing section or term" do
@enrollment.course_section = nil
@course.enrollment_term = nil
expect(@enrollment.effective_start_at).to eq @enrollment.start_at
@enrollment.start_at = nil
expect(@enrollment.effective_start_at).to eq @course.start_at
@course.start_at = nil
expect(@enrollment.effective_start_at).to eq @course.created_at
@course.created_at = nil
expect(@enrollment.effective_start_at).to be_nil
end
end
describe "effective_end_at" do
before :once do
course_with_student(:active_all => true)
@term = @course.enrollment_term
@section = @enrollment.course_section
# 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]])
expect(@enrollment.effective_end_at).to eq @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]])
expect(@enrollment.effective_end_at).to eq @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
expect(@enrollment.effective_end_at).to eq @enrollment.end_at
@enrollment.end_at = nil
expect(@enrollment.effective_end_at).to eq @section.end_at
@section.end_at = nil
expect(@enrollment.effective_end_at).to eq @course.conclude_at
@course.conclude_at = nil
expect(@enrollment.effective_end_at).to eq @term.end_at
@term.end_at = nil
expect(@enrollment.effective_end_at).to be_nil
end
it "should not explode when missing section or term" do
@enrollment.course_section = nil
@course.enrollment_term = nil
expect(@enrollment.effective_end_at).to eq @enrollment.end_at
@enrollment.end_at = nil
expect(@enrollment.effective_end_at).to eq @course.conclude_at
@course.conclude_at = nil
expect(@enrollment.effective_end_at).to 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.where(:id => @user).update_all(:updated_at => 1.day.ago)
@user.reload
expect(@user.cached_current_enrollments).to eq [ @enrollment ]
@enrollment.conclude
@user.reload
expect(@user.cached_current_enrollments).to eq []
end
end
end
describe 'unconclude' do
it 'should add the enrollment to User#cached_current_enrollments' do
enable_cache do
course_with_student active_course: true, enrollment_state: 'completed'
User.where(:id => @student).update_all(:updated_at => 1.day.ago)
@student.reload
expect(@student.cached_current_enrollments).to eq []
@enrollment.unconclude
expect(@student.cached_current_enrollments).to eq [@enrollment]
end
end
end
describe 'observing users' do
before :once do
@student = user_factory(active_all: true)
@parent = user_with_pseudonym(:active_all => true)
@student.observers << @parent
end
it 'should get new observer enrollments when an observed user gets a new enrollment' do
se = course_with_student(:active_all => true, :user => @student)
pe = @parent.observer_enrollments.first
expect(pe).not_to be_nil
expect(pe.course_id).to eql se.course_id
expect(pe.course_section_id).to eql se.course_section_id
expect(pe.workflow_state).to eql se.workflow_state
expect(pe.associated_user_id).to eql se.user_id
end
it 'should default observer enrollments to "active" state' do
course_factory(active_all: true)
@course.enroll_student(@student, :enrollment_state => 'invited')
pe = @parent.observer_enrollments.where(course_id: @course).first
expect(pe).not_to be_nil
expect(pe.workflow_state).to eql 'active'
end
it 'should have their observer enrollments updated when an observed user\'s enrollment is updated' do
se = course_with_student(:user => @student)
pe = @parent.observer_enrollments.first
expect(pe).not_to be_nil
se.invite
se.accept
expect(pe.reload).to be_active
se.complete
expect(pe.reload).to be_completed
end
it 'should not undelete observer enrollments if the student enrollment wasn\'t already deleted' do
se = course_with_student(:user => @student)
pe = @parent.observer_enrollments.first
expect(pe).not_to be_nil
pe.destroy
se.invite
expect(pe.reload).to be_deleted
se.accept
expect(pe.reload).to be_deleted
end
context "sharding" do
specs_require_sharding
it "allows enrolling a user that is observed from another shard" do
se = @shard1.activate do
account = Account.create!
User.any_instance.expects(:can_be_enrolled_in_course?).returns(true)
course_with_student(account: account, active_all: true, user: @student)
end
pe = @parent.observer_enrollments.shard(@shard1).first
expect(pe).not_to be_nil
expect(pe.course_id).to eql se.course_id
expect(pe.course_section_id).to eql se.course_section_id
expect(pe.workflow_state).to eql se.workflow_state
expect(pe.associated_user_id).to eql se.user_id
end
end
end
describe '#can_be_deleted_by' do
describe 'on a student enrollment' do
let(:enrollment) { StudentEnrollment.new }
let(:user) { stub(:id => 42) }
let(:session) { stub }
it 'is true for a user who has been granted :manage_students' do
context = Object.new
context.stubs(:grants_right?).with(user, session, :manage_students).returns(true)
context.stubs(:grants_right?).with(user, session, :manage_admin_users).returns(false)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_truthy
end
it 'is false for a user without :manage_students' do
context = stub(:grants_right? => false)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_falsey
end
it 'is false for someone with :manage_admin_users but without :manage_students' do
context = Object.new
context.stubs(:grants_right?).with(user, session, :manage_students).returns(false)
context.stubs(:grants_right?).with(user, session, :manage_admin_users).returns(true)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_falsey
end
it 'is false if a user is trying to remove their own enrollment' do
context = Object.new
context.stubs(:grants_right?).with(user, session, :manage_students).returns(true)
context.stubs(:grants_right?).with(user, session, :manage_admin_users).returns(false)
context.stubs(:account => context)
enrollment.user_id = user.id
expect(enrollment.can_be_deleted_by(user, context, session)).to be_falsey
end
end
describe 'on an observer enrollment' do
let(:enrollment) { ObserverEnrollment.new }
let(:user) { stub(:id => 42) }
let(:session) { stub }
it 'is true with :manage_students' do
context = Object.new
context.stubs(:grants_right?).with(user, session, :manage_students).returns(true)
context.stubs(:grants_right?).with(user, session, :manage_admin_users).returns(false)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_truthy
end
it 'is true with :manage_admin_users' do
context = Object.new
context.stubs(:grants_right?).with(user, session, :manage_students).returns(false)
context.stubs(:grants_right?).with(user, session, :manage_admin_users).returns(true)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_truthy
end
it 'is false otherwise' do
context = Object.new
context.stubs(:grants_right?).with(user, session, :manage_students).returns(false)
context.stubs(:grants_right?).with(user, session, :manage_admin_users).returns(false)
expect(enrollment.can_be_deleted_by(user, context, session)).to be_falsey
end
end
end
describe "updating cached due dates" do
before :once do
course_with_student
@assignments = [
assignment_model(:course => @course),
assignment_model(:course => @course)
]
end
it "triggers a batch when enrollment is created" do
DueDateCacher.expects(:recompute).never
DueDateCacher.expects(:recompute_course).with(@course)
@course.enroll_student(user_factory)
end
it "does not trigger a batch when enrollment is not student" do
DueDateCacher.expects(:recompute).never
DueDateCacher.expects(:recompute_course).never
@course.enroll_teacher(user_factory)
end
it "triggers a batch when enrollment is deleted" do
DueDateCacher.expects(:recompute).never
DueDateCacher.expects(:recompute_course).with(@course)
@enrollment.destroy
end
it "does not trigger when nothing changed" do
DueDateCacher.expects(:recompute).never
DueDateCacher.expects(:recompute_course).never
@enrollment.save
end
end
describe "#student_with_conditions?" do
it "returns false if the enrollment is neither a student enrollment nor a fake student enrollment" do
@enrollment.stubs(:student?).returns(false)
@enrollment.stubs(:fake_student?).returns(false)
expect(@enrollment.student_with_conditions?(include_future: true, include_fake_student: true)).to eq(false)
end
context "the enrollment is a student enrollment" do
before(:each) do
@enrollment.stubs(:student?).returns(true)
@enrollment.stubs(:fake_student?).returns(false)
end
it "returns true if include_future is true" do
expect(@enrollment.student_with_conditions?(include_future: true, include_fake_student: false)).to eq(true)
end
it "returns true if include_future is false and the enrollment is active" do
@enrollment.stubs(:participating?).returns(true)
expect(@enrollment.student_with_conditions?(include_future: false, include_fake_student: false)).to eq(true)
end
it "returns false if include_future is false and the enrollment is inactive" do
@enrollment.stubs(:participating?).returns(false)
expect(@enrollment.student_with_conditions?(include_future: false, include_fake_student: false)).to eq(false)
end
end
context "the enrollment is a fake student enrollment" do
before(:each) do
@enrollment.stubs(:student?).returns(false)
@enrollment.stubs(:fake_student?).returns(true)
end
it "returns false if include_fake_student is false" do
expect(@enrollment.student_with_conditions?(include_future: true, include_fake_student: false)).to eq(false)
end
context "include_fake_student is passed in as true" do
it "returns true if include_future is true" do
expect(@enrollment.student_with_conditions?(include_future: true, include_fake_student: true)).to eq(true)
end
it "returns true if include_future is false and the enrollment is active" do
@enrollment.stubs(:participating?).returns(true)
expect(@enrollment.student_with_conditions?(include_future: false, include_fake_student: true)).to eq(true)
end
it "returns false if include_future is false and the enrollment is inactive" do
@enrollment.stubs(:participating?).returns(false)
expect(@enrollment.student_with_conditions?(include_future: false, include_fake_student: true)).to eq(false)
end
end
end
end
describe ".not_yet_started" do
before :once do
course_with_student(active_all: true)
end
it 'includes users for whom the enrollment has not yet started' do
Enrollment.any_instance.stubs(:effective_start_at).returns(1.month.from_now)
expect(Enrollment.not_yet_started(@course)).to include(@enrollment)
end
it 'excludes users for whom the enrollment has started' do
@enrollment.stubs(:effective_start_at).returns(1.month.ago)
expect(Enrollment.not_yet_started(@course)).not_to include(@enrollment)
end
end
describe "readable_state_based_on_date" do
before :once do
course_factory(active_all: true)
@enrollment = @course.enroll_student(user_factory)
@enrollment.accept!
end
it "should return pending for future enrollments (even if view restricted)" do
@course.start_at = 1.month.from_now
@course.restrict_student_future_view = true
@course.restrict_enrollments_to_course_dates = true
@course.save!
@enrollment.reload
expect(@enrollment.state_based_on_date).to eq :inactive
expect(@enrollment.readable_state_based_on_date).to eq :pending
@course.restrict_student_future_view = false
@course.save!
@enrollment = Enrollment.find(@enrollment.id)
expect(@enrollment.state_based_on_date).to eq :accepted
expect(@enrollment.readable_state_based_on_date).to eq :pending
end
it "should return completed for completed enrollments (even if view restricted)" do
@course.start_at = 2.months.ago
@course.conclude_at = 1.month.ago
@course.restrict_student_past_view = true
@course.restrict_enrollments_to_course_dates = true
@course.save!
@enrollment.reload
expect(@enrollment.state_based_on_date).to eq :inactive
expect(@enrollment.readable_state_based_on_date).to eq :completed
@course.complete!
@enrollment = Enrollment.find(@enrollment.id)
expect(@enrollment.state_based_on_date).to eq :inactive
expect(@enrollment.readable_state_based_on_date).to eq :completed
@course.restrict_student_past_view = false
@course.save!
@enrollment = Enrollment.find(@enrollment.id)
expect(@enrollment.state_based_on_date).to eq :completed
expect(@enrollment.readable_state_based_on_date).to eq :completed
end
end
describe "update user account associations if necessary" do
it "should create a user_account_association when restoring a deleted enrollment" do
sub_account = Account.default.sub_accounts.create!
course = Course.create!(:account => sub_account)
@enrollment = course.enroll_student(user_factory)
expect(@user.user_account_associations.where(account: sub_account).exists?).to eq true
@enrollment.destroy
expect(@user.user_account_associations.where(account: sub_account).exists?).to eq false
@enrollment.restore
expect(@user.user_account_associations.where(account: sub_account).exists?).to eq true
end
end
it "should order by state based on date correctly" do
u = user_factory(active_all: true)
c1 = course_factory(active_all: true)
c1.start_at = 1.day.from_now
c1.conclude_at = 2.days.from_now
c1.restrict_enrollments_to_course_dates = true
c1.restrict_student_future_view = true
c1.save!
restricted_enroll = c1.enroll_student(u)
c2 = course_factory(active_all: true)
c2.start_at = 1.day.from_now
c2.conclude_at = 2.days.from_now
c2.restrict_enrollments_to_course_dates = true
c2.save!
future_enroll = c2.enroll_student(u)
c3 = course_factory(active_all: true)
active_enroll = c3.enroll_student(u)
[restricted_enroll, future_enroll, active_enroll].each do |e|
e.workflow_state = 'active'
e.save!
end
enrolls = Enrollment.where(:id => [restricted_enroll, future_enroll, active_enroll]).
joins(:enrollment_state).order(Enrollment.state_by_date_rank_sql).to_a
expect(enrolls).to eq [active_enroll, future_enroll, restricted_enroll]
end
end