457 lines
18 KiB
Ruby
457 lines
18 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 File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
|
|
|
describe CourseSection, "moving to new course" do
|
|
|
|
it "should transfer enrollments to the new root account" do
|
|
account1 = Account.create!(:name => "1")
|
|
account2 = Account.create!(:name => "2")
|
|
course1 = account1.courses.create!
|
|
course2 = account2.courses.create!
|
|
course3 = account2.courses.create!
|
|
cs = course1.course_sections.create!
|
|
u = User.create!
|
|
u.register!
|
|
e = course1.enroll_user(u, 'StudentEnrollment', :section => cs)
|
|
e.workflow_state = 'active'
|
|
e.save!
|
|
course1.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(e.root_account).to eql(account1)
|
|
expect(e.course).to eql(course1)
|
|
|
|
cs.move_to_course(course2)
|
|
course1.reload
|
|
course2.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(e.root_account).to eql(account2)
|
|
expect(e.course).to eql(course2)
|
|
|
|
cs.move_to_course(course3)
|
|
course1.reload
|
|
course2.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(e.root_account).to eql(account2)
|
|
expect(e.course).to eql(course3)
|
|
|
|
cs.move_to_course(course1)
|
|
course1.reload
|
|
course2.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(e.root_account).to eql(account1)
|
|
expect(e.course).to eql(course1)
|
|
|
|
cs.move_to_course(course1)
|
|
course1.reload
|
|
course2.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(e.root_account).to eql(account1)
|
|
expect(e.course).to eql(course1)
|
|
end
|
|
|
|
it "should associate a section with the course's account" do
|
|
account = Account.default.manually_created_courses_account
|
|
course = account.courses.create!
|
|
section = course.default_section
|
|
expect(section.course_account_associations.map(&:account_id).sort).to eq [Account.default.id, account.id].sort
|
|
end
|
|
|
|
it "should update user account associations for xlist between subaccounts" do
|
|
root_account = Account.create!(:name => "root")
|
|
sub_account1 = Account.create!(:parent_account => root_account, :name => "account1")
|
|
sub_account2 = Account.create!(:parent_account => root_account, :name => "account2")
|
|
sub_account3 = Account.create!(:parent_account => root_account, :name => "account3")
|
|
course1 = sub_account1.courses.create!(:name => "course1")
|
|
course2 = sub_account2.courses.create!(:name => "course2")
|
|
course3 = sub_account3.courses.create!(:name => "course3")
|
|
cs = course1.course_sections.create!
|
|
expect(cs.nonxlist_course_id).to be_nil
|
|
u = User.create!
|
|
u.register!
|
|
e = course1.enroll_user(u, 'StudentEnrollment', :section => cs)
|
|
u.update_account_associations
|
|
|
|
expect(course1.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id].sort
|
|
expect(course2.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account2.id].sort
|
|
expect(course3.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account3.id].sort
|
|
u.reload
|
|
expect(u.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id].sort
|
|
|
|
cs.crosslist_to_course(course2)
|
|
expect(course1.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id].sort
|
|
expect(course2.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id, sub_account2.id].sort
|
|
expect(course3.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account3.id].sort
|
|
u.reload
|
|
expect(u.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id, sub_account2.id].sort
|
|
|
|
cs.crosslist_to_course(course3)
|
|
expect(cs.nonxlist_course_id).to eq course1.id
|
|
expect(course1.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id].sort
|
|
expect(course2.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account2.id].sort
|
|
expect(course3.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id, sub_account3.id].sort
|
|
u.reload
|
|
expect(u.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id, sub_account3.id].sort
|
|
|
|
cs.uncrosslist
|
|
expect(cs.nonxlist_course_id).to be_nil
|
|
expect(course1.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id].sort
|
|
expect(course2.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account2.id].sort
|
|
expect(course3.reload.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account3.id].sort
|
|
u.reload
|
|
expect(u.associated_accounts.map(&:id).sort).to eq [root_account.id, sub_account1.id]
|
|
end
|
|
|
|
it "should crosslist and uncrosslist" do
|
|
account1 = Account.create!(:name => "1")
|
|
account2 = Account.create!(:name => "2")
|
|
account3 = Account.create!(:name => "3")
|
|
course1 = account1.courses.create!
|
|
course2 = account2.courses.create!
|
|
course3 = account3.courses.create!
|
|
course2.assert_section
|
|
course3.assert_section
|
|
cs = course1.course_sections.create!
|
|
u = User.create!
|
|
u.register!
|
|
e = course2.enroll_user(u, 'StudentEnrollment')
|
|
e.workflow_state = 'active'
|
|
e.save!
|
|
e = course1.enroll_user(u, 'StudentEnrollment', :section => cs)
|
|
e.workflow_state = 'active'
|
|
e.save!
|
|
#should also move deleted enrollments
|
|
e.destroy
|
|
course1.reload
|
|
course2.reload
|
|
course3.workflow_state = 'active'
|
|
course3.save
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(cs.nonxlist_course).to be_nil
|
|
expect(e.root_account).to eql(account1)
|
|
expect(cs.crosslisted?).to be_falsey
|
|
expect(course1.workflow_state).to eq 'created'
|
|
expect(course2.workflow_state).to eq 'created'
|
|
expect(course3.workflow_state).to eq 'created'
|
|
|
|
cs.crosslist_to_course(course2)
|
|
course1.reload
|
|
course2.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(cs.nonxlist_course).to eql(course1)
|
|
expect(e.root_account).to eql(account2)
|
|
expect(cs.crosslisted?).to be_truthy
|
|
expect(course1.workflow_state).to eq 'created'
|
|
expect(course2.workflow_state).to eq 'created'
|
|
expect(course3.workflow_state).to eq 'created'
|
|
|
|
cs.crosslist_to_course(course3)
|
|
course1.reload
|
|
course2.reload
|
|
course3.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(cs.nonxlist_course).to eql(course1)
|
|
expect(e.root_account).to eql(account3)
|
|
expect(cs.crosslisted?).to be_truthy
|
|
expect(course1.workflow_state).to eq 'created'
|
|
expect(course2.workflow_state).to eq 'created'
|
|
expect(course3.workflow_state).to eq 'created'
|
|
|
|
cs.uncrosslist
|
|
course1.reload
|
|
course2.reload
|
|
course3.reload
|
|
cs.reload
|
|
e.reload
|
|
|
|
expect(course1.course_sections.where(id: cs).first).not_to be_nil
|
|
expect(course2.course_sections.where(id: cs).first).to be_nil
|
|
expect(course3.course_sections.where(id: cs).first).to be_nil
|
|
expect(cs.nonxlist_course).to be_nil
|
|
expect(e.root_account).to eql(account1)
|
|
expect(cs.crosslisted?).to be_falsey
|
|
expect(course1.workflow_state).to eq 'created'
|
|
expect(course2.workflow_state).to eq 'created'
|
|
expect(course3.workflow_state).to eq 'created'
|
|
end
|
|
|
|
describe '#delete_enrollments_if_deleted' do
|
|
let(:account) { Account.create!(name: '1') }
|
|
let(:course) { account.courses.create! }
|
|
let(:section) { course.course_sections.create!(workflow_state: 'active') }
|
|
|
|
before do
|
|
student_in_section(section)
|
|
end
|
|
|
|
it "must not have any effect when the section's workflow_state is not 'deleted'" do
|
|
section.delete_enrollments_if_deleted
|
|
enrollment_states = section.enrollments.pluck(:workflow_state)
|
|
expect(enrollment_states).to_not include 'deleted'
|
|
end
|
|
|
|
it "must mark the enrollments as deleted if the section's workflow_state is 'deleted'" do
|
|
section.workflow_state = 'deleted'
|
|
section.delete_enrollments_if_deleted
|
|
enrollment_states = Enrollment.where(course_section_id: section.id).pluck(:workflow_state)
|
|
expect(enrollment_states.uniq).to contain_exactly 'deleted'
|
|
end
|
|
end
|
|
|
|
it 'should update course account associations on save' do
|
|
account1 = Account.create!(:name => "1")
|
|
account2 = account1.sub_accounts.create!(:name => "2")
|
|
course1 = account1.courses.create!
|
|
course2 = account2.courses.create!
|
|
cs1 = course1.course_sections.create!
|
|
expect(CourseAccountAssociation.where(course_id: course1).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id]
|
|
expect(CourseAccountAssociation.where(course_id: course2).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id, account2.id]
|
|
course1.account = account2
|
|
course1.save
|
|
expect(CourseAccountAssociation.where(course_id: course1).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id, account2.id].sort
|
|
expect(CourseAccountAssociation.where(course_id: course2).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id, account2.id]
|
|
course1.account = nil
|
|
course1.save
|
|
expect(CourseAccountAssociation.where(course_id: course1).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id]
|
|
expect(CourseAccountAssociation.where(course_id: course2).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id, account2.id]
|
|
cs1.crosslist_to_course(course2)
|
|
expect(CourseAccountAssociation.where(course_id: course1).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id]
|
|
expect(CourseAccountAssociation.where(course_id: course2).distinct.order(:account_id).pluck(:account_id)).to eq [account1.id, account2.id].sort
|
|
end
|
|
|
|
it 'should call course#recompute_student_scores_without_send_later if :run_jobs_immediately' do
|
|
account1 = Account.create!(:name => "1")
|
|
account2 = Account.create!(:name => "2")
|
|
course1 = account1.courses.create!
|
|
course2 = account2.courses.create!
|
|
cs = course1.course_sections.create!
|
|
u = User.create!
|
|
u.register!
|
|
e = course1.enroll_user(u, 'StudentEnrollment', :section => cs)
|
|
e.workflow_state = 'active'
|
|
e.save!
|
|
course1.reload
|
|
|
|
expect(course2).to receive(:recompute_student_scores_without_send_later)
|
|
cs.move_to_course(course2, run_jobs_immediately: true)
|
|
end
|
|
|
|
it 'should call course##recompute_student_scores later without :run_jobs_immediately' do
|
|
account1 = Account.create!(:name => "1")
|
|
account2 = Account.create!(:name => "2")
|
|
course1 = account1.courses.create!
|
|
course2 = account2.courses.create!
|
|
cs = course1.course_sections.create!
|
|
u = User.create!
|
|
u.register!
|
|
e = course1.enroll_user(u, 'StudentEnrollment', :section => cs)
|
|
e.workflow_state = 'active'
|
|
e.save!
|
|
course1.reload
|
|
|
|
expect(course2).to receive(:recompute_student_scores)
|
|
cs.move_to_course(course2)
|
|
end
|
|
|
|
describe 'validation' do
|
|
before :once do
|
|
course = Course.create_unique
|
|
@section = CourseSection.create(course: course)
|
|
@long_string = 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'
|
|
end
|
|
|
|
it "should validate the length of attributes" do
|
|
@section.name = @long_string
|
|
@section.sis_source_id = @long_string
|
|
expect(lambda {@section.save!}).to raise_error("Validation failed: Sis source is too long (maximum is 255 characters), Name is too long (maximum is 255 characters)")
|
|
end
|
|
|
|
it "should validate the length of sis_source_id" do
|
|
@section.sis_source_id = @long_string
|
|
expect(lambda {@section.save!}).to raise_error("Validation failed: Sis source is too long (maximum is 255 characters)")
|
|
end
|
|
|
|
it "should validate the length of section name" do
|
|
@section.name = @long_string
|
|
expect(lambda {@section.save!}).to raise_error("Validation failed: Name is too long (maximum is 255 characters)")
|
|
end
|
|
end
|
|
|
|
describe 'dependent destroys' do
|
|
before :once do
|
|
course_with_teacher
|
|
@course.assignments.build
|
|
@assignment = @course.assignments.build("title"=>"test")
|
|
@assignment.save!
|
|
@section = @course.course_sections.create!
|
|
end
|
|
|
|
it 'should soft destroy overrides when destroyed' do
|
|
@override = @assignment.assignment_overrides.build
|
|
@override.set = @section
|
|
@override.save!
|
|
expect(@override.workflow_state).to eq("active")
|
|
@section.destroy
|
|
@override.reload
|
|
expect(@override.workflow_state).to eq("deleted")
|
|
end
|
|
|
|
it 'should soft destroy enrollments when destroyed' do
|
|
@enrollment = @course.enroll_student(User.create, {section: @section})
|
|
expect(@enrollment.workflow_state).to eq("creation_pending")
|
|
@section.destroy
|
|
@enrollment.reload
|
|
expect(@enrollment.workflow_state).to eq("deleted")
|
|
end
|
|
end
|
|
|
|
describe 'deletable?' do
|
|
before :once do
|
|
course_with_teacher
|
|
@section = @course.course_sections.create!
|
|
end
|
|
|
|
it 'should be deletable if empty' do
|
|
expect(@section).to be_deletable
|
|
end
|
|
|
|
it 'should not be deletable if it has real enrollments' do
|
|
student_in_course :section => @section
|
|
expect(@section).not_to be_deletable
|
|
end
|
|
|
|
it 'should be deletable if it only has a student view enrollment' do
|
|
@course.student_view_student
|
|
expect(@section.enrollments.map(&:type)).to eql ['StudentViewEnrollment']
|
|
expect(@section).to be_deletable
|
|
end
|
|
|
|
it 'should be deletable if it only has rejected enrollments' do
|
|
student_in_course :section => @section
|
|
@section.enrollments.first.update_attribute(:workflow_state, "rejected")
|
|
expect(@section).to be_deletable
|
|
end
|
|
end
|
|
|
|
context 'permissions' do
|
|
context ':read and section_visibilities' do
|
|
before :once do
|
|
RoleOverride.create!({
|
|
:context => Account.default,
|
|
:permission => 'manage_students',
|
|
:role => ta_role,
|
|
:enabled => false
|
|
})
|
|
course_with_ta(:active_all => true)
|
|
@other_section = @course.course_sections.create!(:name => "Other Section")
|
|
end
|
|
|
|
it "should work with section_limited true" do
|
|
@ta.enrollments.update_all(:limit_privileges_to_course_section => true)
|
|
@ta.reload
|
|
|
|
# make sure other ways to get :read are false
|
|
expect(@other_section.course.grants_right?(@ta, :manage_sections)).to be_falsey
|
|
expect(@other_section.course.grants_right?(@ta, :manage_students)).to be_falsey
|
|
|
|
expect(@other_section.grants_right?(@ta, :read)).to be_falsey
|
|
end
|
|
|
|
it "should work with section_limited false" do
|
|
# make sure other ways to get :read are false
|
|
expect(@other_section.course.grants_right?(@ta, :manage_sections)).to be_falsey
|
|
expect(@other_section.course.grants_right?(@ta, :manage_students)).to be_falsey
|
|
|
|
expect(@other_section.grants_right?(@ta, :read)).to be_truthy
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'enrollment state invalidation' do
|
|
before :once do
|
|
course_factory(active_all: true)
|
|
@section = @course.course_sections.create!
|
|
@enrollment = @course.enroll_student(user_factory(:active_all => true), :section => @section)
|
|
end
|
|
|
|
it "should not invalidate unless something date-related changes" do
|
|
EnrollmentState.expects(:update_enrollment).never
|
|
@section.name = "durp"
|
|
@section.save!
|
|
end
|
|
|
|
it "should not invalidate if dates change if it isn't restricted to dates yet" do
|
|
EnrollmentState.expects(:update_enrollment).never
|
|
@section.start_at = 1.day.from_now
|
|
@section.save!
|
|
end
|
|
|
|
it "should invalidate if dates change and section is restricted to dates" do
|
|
@section.restrict_enrollments_to_section_dates = true
|
|
@section.save!
|
|
EnrollmentState.expects(:update_enrollment).with(@enrollment).once
|
|
@section.start_at = 1.day.from_now
|
|
@section.save!
|
|
end
|
|
|
|
it "should invalidate if course" do
|
|
other_course = course_factory(active_all: true)
|
|
EnrollmentState.expects(:update_enrollment).with(@enrollment).once
|
|
@section.crosslist_to_course(other_course)
|
|
end
|
|
end
|
|
end
|