canvas-lms/spec/models/course_spec.rb

2691 lines
121 KiB
Ruby

#
# Copyright (C) 2012 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
require 'socket'
describe Course do
before(:each) do
@course = Course.new
end
context "validation" do
it "should create a new instance given valid attributes" do
course_model
end
end
it "should create a unique course." do
@course = Course.create_unique
@course.name.should eql("My Course")
@uuid = @course.uuid
@course2 = Course.create_unique(@uuid)
@course.should eql(@course2)
end
it "should always have a uuid, if it was created" do
@course.save!
@course.uuid.should_not be_nil
end
context "permissions" do
it "should follow account chain when looking for generic permissions from AccountUsers" do
account = Account.create!
sub_account = Account.create!(:parent_account => account)
sub_sub_account = Account.create!(:parent_account => sub_account)
user = account_admin_user(:account => sub_account)
course = Course.create!(:account => sub_sub_account)
course.grants_right?(user, nil, :manage).should be_true
end
it "should grant delete to the proper individuals" do
account_admin_user_with_role_changes(:membership_type => 'managecourses', :role_changes => {:manage_courses => true})
@admin1 = @admin
account_admin_user_with_role_changes(:membership_type => 'managesis', :role_changes => {:manage_sis => true})
@admin2 = @admin
course_with_teacher(:active_all => true)
@designer = user(:active_all => true)
@course.enroll_designer(@designer).accept!
@course.grants_right?(@teacher, nil, :delete).should be_true
@course.grants_right?(@designer, nil, :delete).should be_true
@course.grants_right?(@admin1, nil, :delete).should be_true
@course.grants_right?(@admin2, nil, :delete).should be_false
@course.complete!
@course.grants_right?(@teacher, nil, :delete).should be_true
@course.grants_right?(@designer, nil, :delete).should be_true
@course.grants_right?(@admin1, nil, :delete).should be_true
@course.grants_right?(@admin2, nil, :delete).should be_false
@course.sis_source_id = 'sis_id'
@course.save!
@course.grants_right?(@teacher, nil, :delete).should be_false
@course.grants_right?(@designer, nil, :delete).should be_false
@course.grants_right?(@admin1, nil, :delete).should be_true
@course.grants_right?(@admin2, nil, :delete).should be_true
end
def make_date_completed
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
@enrollment.state_based_on_date.should == :completed
end
it "should grant read_as_admin and read_forum to date-completed teacher" do
course_with_teacher(:active_all => 1)
make_date_completed
@course.prior_enrollments.should == []
@course.grants_right?(@teacher, nil, :read_as_admin).should be_true
@course.grants_right?(@teacher, nil, :read_forum).should be_true
end
it "should grant read_as_admin, read, manage, and update to date-active designer" do
course(:active_all => 1)
@designer = user(:active_all => 1)
@course.enroll_designer(@designer).accept!
@course.grants_right?(@designer, nil, :read_as_admin).should be_true
@course.grants_right?(@designer, nil, :read).should be_true
@course.grants_right?(@designer, nil, :manage).should be_true
@course.grants_right?(@designer, nil, :update).should be_true
end
it "should grant read_as_admin, read_roster, and read_prior_roster to date-completed designer" do
course(:active_all => 1)
@designer = user(:active_all => 1)
@enrollment = @course.enroll_designer(@designer)
@enrollment.accept!
@enrollment.start_at = 4.days.ago
@enrollment.end_at = 2.days.ago
@enrollment.save!
@enrollment.state_based_on_date.should == :completed
@course.prior_enrollments.should == []
@course.grants_right?(@designer, nil, :read_as_admin).should be_true
@course.grants_right?(@designer, nil, :read_roster).should be_true
@course.grants_right?(@designer, nil, :read_prior_roster).should be_true
end
it "should not grant read_user_notes or view_all_grades to designer" do
course(:active_all => 1)
@designer = user(:active_all => 1)
@course.enroll_designer(@designer).accept!
@course.grants_right?(@designer, nil, :read_user_notes).should be_false
@course.grants_right?(@designer, nil, :view_all_grades).should be_false
end
it "should grant read_grades read_forum to date-completed student" do
course_with_student(:active_all => 1)
make_date_completed
@course.prior_enrollments.should == []
@course.grants_right?(@student, nil, :read_grades).should be_true
@course.grants_right?(@student, nil, :read_forum).should be_true
end
end
it "should clear content when resetting" do
course_with_student
@course.discussion_topics.create!
@course.quizzes.create!
@course.assignments.create!
@course.wiki.wiki_page.save!
@course.sis_source_id = 'sis_id'
@course.stuck_sis_fields = [].to_set
@course.save!
@course.course_sections.should_not be_empty
@course.students.should == [@student]
@course.stuck_sis_fields.should == [].to_set
@new_course = @course.reset_content
@course.reload
@course.stuck_sis_fields.should == [].to_set
@course.course_sections.should be_empty
@course.students.should be_empty
@course.sis_source_id.should be_nil
@new_course.reload
@new_course.course_sections.should_not be_empty
@new_course.students.should == [@student]
@new_course.discussion_topics.should be_empty
@new_course.quizzes.should be_empty
@new_course.assignments.should be_empty
@new_course.sis_source_id.should == 'sis_id'
@new_course.syllabus_body.should be_blank
@new_course.stuck_sis_fields.should == [].to_set
@course.uuid.should_not == @new_course.uuid
@course.wiki_id.should_not == @new_course.wiki_id
@course.replacement_course_id.should == @new_course.id
end
it "should preserve sticky fields when resetting content" do
course_with_student
@course.sis_source_id = 'sis_id'
@course.course_code = "cid"
@course.save!
@course.stuck_sis_fields = [].to_set
@course.name = "course_name"
@course.stuck_sis_fields.should == [:name].to_set
@course.save!
@course.stuck_sis_fields.should == [:name].to_set
@new_course = @course.reset_content
@course.reload
@course.stuck_sis_fields.should == [:name].to_set
@course.sis_source_id.should be_nil
@new_course.reload
@new_course.sis_source_id.should == 'sis_id'
@new_course.stuck_sis_fields.should == [:name].to_set
@course.uuid.should_not == @new_course.uuid
@course.replacement_course_id.should == @new_course.id
end
it "group_categories should not include deleted categories" do
course = course_model
course.group_categories.count.should == 0
category1 = course.group_categories.create(:name => 'category 1')
category2 = course.group_categories.create(:name => 'category 2')
course.group_categories.count.should == 2
category1.destroy
course.reload
course.group_categories.count.should == 1
course.group_categories.to_a.should == [category2]
end
it "all_group_categories should include deleted categories" do
course = course_model
course.all_group_categories.count.should == 0
category1 = course.group_categories.create(:name => 'category 1')
category2 = course.group_categories.create(:name => 'category 2')
course.all_group_categories.count.should == 2
category1.destroy
course.reload
course.all_group_categories.count.should == 2
end
context "users_not_in_groups" do
before :each do
@course = course(:active_all => true)
@user1 = user_model
@user2 = user_model
@user3 = user_model
@enrollment1 = @course.enroll_user(@user1)
@enrollment2 = @course.enroll_user(@user2)
@enrollment3 = @course.enroll_user(@user3)
end
it "should not include users through deleted/rejected/completed enrollments" do
@enrollment1.destroy
@course.users_not_in_groups([]).size.should == 2
end
it "should not include users in one of the groups" do
group = @course.groups.create
group.add_user(@user1)
users = @course.users_not_in_groups([group])
users.size.should == 2
users.should_not be_include(@user1)
end
it "should include users otherwise" do
group = @course.groups.create
group.add_user(@user1)
users = @course.users_not_in_groups([group])
users.should be_include(@user2)
users.should be_include(@user3)
end
end
it "should order results of paginate_users_not_in_groups by user's sortable name" do
@course = course(:active_all => true)
@user1 = user_model; @user1.sortable_name = 'jonny'; @user1.save
@user2 = user_model; @user2.sortable_name = 'bob'; @user2.save
@user3 = user_model; @user3.sortable_name = 'richard'; @user3.save
@course.enroll_user(@user1)
@course.enroll_user(@user2)
@course.enroll_user(@user3)
users = @course.paginate_users_not_in_groups([], 1)
users.map{ |u| u.id }.should == [@user2.id, @user1.id, @user3.id]
end
end
describe Course, "enroll" do
before(:each) do
@course = Course.create(:name => "some_name")
@user = user_with_pseudonym
end
it "should be able to enroll a student" do
@course.enroll_student(@user)
@se = @course.student_enrollments.first
@se.user_id.should eql(@user.id)
@se.course_id.should eql(@course.id)
end
it "should be able to enroll a TA" do
@course.enroll_ta(@user)
@tae = @course.ta_enrollments.first
@tae.user_id.should eql(@user.id)
@tae.course_id.should eql(@course.id)
end
it "should be able to enroll a teacher" do
@course.enroll_teacher(@user)
@te = @course.teacher_enrollments.first
@te.user_id.should eql(@user.id)
@te.course_id.should eql(@course.id)
end
it "should be able to enroll a designer" do
@course.enroll_designer(@user)
@de = @course.designer_enrollments.first
@de.user_id.should eql(@user.id)
@de.course_id.should eql(@course.id)
end
it "should enroll a student as creation_pending if the course isn't published" do
@se = @course.enroll_student(@user)
@se.user_id.should eql(@user.id)
@se.course_id.should eql(@course.id)
@se.should be_creation_pending
end
it "should enroll a teacher as invited if the course isn't published" do
Notification.create(:name => "Enrollment Registration", :category => "registration")
@tae = @course.enroll_ta(@user)
@tae.user_id.should eql(@user.id)
@tae.course_id.should eql(@course.id)
@tae.should be_invited
@tae.messages_sent.should be_include("Enrollment Registration")
end
it "should enroll a ta as invited if the course isn't published" do
Notification.create(:name => "Enrollment Registration", :category => "registration")
@te = @course.enroll_teacher(@user)
@te.user_id.should eql(@user.id)
@te.course_id.should eql(@course.id)
@te.should be_invited
@te.messages_sent.should be_include("Enrollment Registration")
end
end
describe Course, "score_to_grade" do
it "should correctly map scores to grades" do
default = GradingStandard.default_grading_standard
default.to_json.should eql([["A", 0.94], ["A-", 0.90], ["B+", 0.87], ["B", 0.84], ["B-", 0.80], ["C+", 0.77], ["C", 0.74], ["C-", 0.70], ["D+", 0.67], ["D", 0.64], ["D-", 0.61], ["F", 0]].to_json)
course_model
@course.score_to_grade(95).should eql(nil)
@course.grading_standard_id = 0
@course.score_to_grade(1005).should eql("A")
@course.score_to_grade(105).should eql("A")
@course.score_to_grade(100).should eql("A")
@course.score_to_grade(99).should eql("A")
@course.score_to_grade(94).should eql("A")
@course.score_to_grade(93.999).should eql("A-")
@course.score_to_grade(93.001).should eql("A-")
@course.score_to_grade(93).should eql("A-")
@course.score_to_grade(92.999).should eql("A-")
@course.score_to_grade(90).should eql("A-")
@course.score_to_grade(89).should eql("B+")
@course.score_to_grade(87).should eql("B+")
@course.score_to_grade(86).should eql("B")
@course.score_to_grade(85).should eql("B")
@course.score_to_grade(83).should eql("B-")
@course.score_to_grade(80).should eql("B-")
@course.score_to_grade(79).should eql("C+")
@course.score_to_grade(76).should eql("C")
@course.score_to_grade(73).should eql("C-")
@course.score_to_grade(71).should eql("C-")
@course.score_to_grade(69).should eql("D+")
@course.score_to_grade(67).should eql("D+")
@course.score_to_grade(66).should eql("D")
@course.score_to_grade(65).should eql("D")
@course.score_to_grade(62).should eql("D-")
@course.score_to_grade(60).should eql("F")
@course.score_to_grade(59).should eql("F")
@course.score_to_grade(0).should eql("F")
@course.score_to_grade(-100).should eql("F")
end
end
describe Course, "gradebook_to_csv" do
it "should generate gradebook csv" do
course_with_student(:active_all => true)
@group = @course.assignment_groups.create!(:name => "Some Assignment Group", :group_weight => 100)
@assignment = @course.assignments.create!(:title => "Some Assignment", :points_possible => 10, :assignment_group => @group)
@assignment.grade_student(@user, :grade => "10")
@assignment2 = @course.assignments.create!(:title => "Some Assignment 2", :points_possible => 10, :assignment_group => @group)
@course.recompute_student_scores
@user.reload
@course.reload
csv = @course.gradebook_to_csv
csv.should_not be_nil
rows = FasterCSV.parse(csv)
rows.length.should equal(3)
rows[0][-1].should == "Final Score"
rows[1][-1].should == "(read only)"
rows[2][-1].should == "50"
rows[0][-2].should == "Current Score"
rows[1][-2].should == "(read only)"
rows[2][-2].should == "100"
end
it "should order assignments by due date, assignment_group, position, title" do
course_with_student(:active_all => true)
@assignment_group_1, @assignment_group_2 = [@course.assignment_groups.create!(:name => "Some Assignment Group 1", :group_weight => 100), @course.assignment_groups.create!(:name => "Some Assignment Group 2", :group_weight => 100)].sort_by{|a| a.id}
now = Time.now
@assignment = @course.assignments.create!(:title => "Some Assignment 01", :points_possible => 10, :due_at => now + 1.days, :position => 3, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 02", :points_possible => 10, :due_at => now + 1.days, :position => 1, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 03", :points_possible => 10, :due_at => now + 1.days, :position => 2, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 05", :points_possible => 10, :due_at => now + 4.days, :position => 4, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 04", :points_possible => 10, :due_at => now + 5.days, :position => 5, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 06", :points_possible => 10, :due_at => now + 7.days, :position => 6, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 07", :points_possible => 10, :due_at => now + 6.days, :position => 7, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 08", :points_possible => 10, :due_at => now + 8.days, :position => 1, :assignment_group => @assignment_group_2)
@assignment = @course.assignments.create!(:title => "Some Assignment 09", :points_possible => 10, :due_at => now + 8.days, :position => 9, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 10", :points_possible => 10, :due_at => now + 8.days, :position => 10, :assignment_group => @assignment_group_2)
@assignment = @course.assignments.create!(:title => "Some Assignment 11", :points_possible => 10, :due_at => now + 11.days, :position => 11, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 13", :points_possible => 10, :due_at => now + 11.days, :position => 11, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 12", :points_possible => 10, :due_at => now + 11.days, :position => 11, :assignment_group => @assignment_group_1)
@assignment = @course.assignments.create!(:title => "Some Assignment 14", :points_possible => 10, :due_at => nil, :position => 14, :assignment_group => @assignment_group_1)
@course.recompute_student_scores
@user.reload
@course.reload
csv = @course.gradebook_to_csv
csv.should_not be_nil
rows = FasterCSV.parse(csv)
rows.length.should equal(3)
assignments = []
rows[0].each do |column|
assignments << column.sub(/ \([0-9]+\)/, '') if column =~ /Some Assignment/
end
assignments.should == ["Some Assignment 14", "Some Assignment 02", "Some Assignment 03", "Some Assignment 01", "Some Assignment 05", "Some Assignment 04", "Some Assignment 07", "Some Assignment 06", "Some Assignment 09", "Some Assignment 08", "Some Assignment 10", "Some Assignment 11", "Some Assignment 12", "Some Assignment 13"]
end
it "should work for just one assignment" do
course_with_student(:active_all => true)
now = Time.now
@assignment = @course.assignments.create!(:title => "Some Assignment 1", :points_possible => 10, :assignment_group => @group, :due_at => now + 1.days, :position => 3)
@assignment = @course.assignments.create!(:title => "Some Assignment 2", :points_possible => 10, :assignment_group => @group, :due_at => now + 1.days, :position => 1)
@course.recompute_student_scores
@user.reload
@course.reload
csv = @course.gradebook_to_csv :assignment_id => @assignment
csv.should_not be_nil
rows = FasterCSV.parse(csv)
rows.length.should equal(3)
assignments = []
rows[0].each do |column|
assignments << column.sub(/ \([0-9]+\)/, '') if column =~ /Some Assignment/
end
assignments.should == ["Some Assignment 2"]
end
it "should generate csv with final grade if enabled" do
course_with_student(:active_all => true)
@course.grading_standard_id = 0
@course.save!
@group = @course.assignment_groups.create!(:name => "Some Assignment Group", :group_weight => 100)
@assignment = @course.assignments.create!(:title => "Some Assignment", :points_possible => 10, :assignment_group => @group)
@assignment.grade_student(@user, :grade => "10")
@assignment2 = @course.assignments.create!(:title => "Some Assignment 2", :points_possible => 10, :assignment_group => @group)
@assignment2.grade_student(@user, :grade => "8")
@course.recompute_student_scores
@user.reload
@course.reload
csv = @course.gradebook_to_csv
csv.should_not be_nil
rows = FasterCSV.parse(csv)
rows.length.should equal(3)
rows[0][-1].should == "Final Grade"
rows[1][-1].should == "(read only)"
rows[2][-1].should == "A-"
rows[0][-2].should == "Final Score"
rows[1][-2].should == "(read only)"
rows[2][-2].should == "90"
rows[0][-3].should == "Current Score"
rows[1][-3].should == "(read only)"
rows[2][-3].should == "90"
end
it "should include sis ids if enabled" do
course(:active_all => true)
@user1 = user_with_pseudonym(:active_all => true, :name => 'Brian', :username => 'brianp@instructure.com')
student_in_course(:user => @user1)
@user2 = user_with_pseudonym(:active_all => true, :name => 'Cody', :username => 'cody@instructure.com')
student_in_course(:user => @user2)
@user3 = user(:active_all => true, :name => 'JT')
student_in_course(:user => @user3)
@user1.pseudonym.sis_user_id = "SISUSERID"
@user1.pseudonym.save!
@group = @course.assignment_groups.create!(:name => "Some Assignment Group", :group_weight => 100)
@assignment = @course.assignments.create!(:title => "Some Assignment", :points_possible => 10, :assignment_group => @group)
@assignment.grade_student(@user1, :grade => "10")
@assignment.grade_student(@user2, :grade => "9")
@assignment.grade_student(@user3, :grade => "9")
@assignment2 = @course.assignments.create!(:title => "Some Assignment 2", :points_possible => 10, :assignment_group => @group)
@course.recompute_student_scores
@course.reload
csv = @course.gradebook_to_csv(:include_sis_id => true)
csv.should_not be_nil
rows = FasterCSV.parse(csv)
rows.length.should == 5
rows[0][1].should == 'ID'
rows[0][2].should == 'SIS User ID'
rows[0][3].should == 'SIS Login ID'
rows[0][4].should == 'Section'
rows[1][2].should == ''
rows[1][3].should == ''
rows[1][4].should == ''
rows[1][-1].should == '(read only)'
rows[2][1].should == @user1.id.to_s
rows[2][2].should == 'SISUSERID'
rows[2][3].should == @user1.pseudonym.unique_id
rows[3][1].should == @user2.id.to_s
rows[3][2].should be_nil
rows[3][3].should == @user2.pseudonym.unique_id
rows[4][1].should == @user3.id.to_s
rows[4][2].should be_nil
rows[4][3].should be_nil
end
it "should only include students once" do
# students might have multiple enrollments in a course
course(:active_all => true)
@user1 = user_with_pseudonym(:active_all => true, :name => 'Brian', :username => 'brianp@instructure.com')
student_in_course(:user => @user1)
@user2 = user_with_pseudonym(:active_all => true, :name => 'Cody', :username => 'cody@instructure.com')
student_in_course(:user => @user2)
@s2 = @course.course_sections.create!(:name => 'section2')
StudentEnrollment.create!(:user => @user1, :course => @course, :course_section => @s2)
@course.reload
csv = @course.gradebook_to_csv(:include_sis_id => true)
rows = FasterCSV.parse(csv)
rows.length.should == 4
end
it "should include muted if any assignments are muted" do
course(:active_all => true)
@user1 = user_with_pseudonym(:active_all => true, :name => 'Brian', :username => 'brianp@instructure.com')
student_in_course(:user => @user1)
@user2 = user_with_pseudonym(:active_all => true, :name => 'Cody', :username => 'cody@instructure.com')
student_in_course(:user => @user2)
@user3 = user(:active_all => true, :name => 'JT')
student_in_course(:user => @user3)
@user1.pseudonym.sis_user_id = "SISUSERID"
@user1.pseudonym.save!
@group = @course.assignment_groups.create!(:name => "Some Assignment Group", :group_weight => 100)
@assignment = @course.assignments.create!(:title => "Some Assignment", :points_possible => 10, :assignment_group => @group)
@assignment.muted = true
@assignment.save!
@assignment.grade_student(@user1, :grade => "10")
@assignment.grade_student(@user2, :grade => "9")
@assignment.grade_student(@user3, :grade => "9")
@assignment2 = @course.assignments.create!(:title => "Some Assignment 2", :points_possible => 10, :assignment_group => @group)
@course.recompute_student_scores
@course.reload
csv = @course.gradebook_to_csv(:include_sis_id => true)
csv.should_not be_nil
rows = FasterCSV.parse(csv)
rows.length.should == 6
rows[0][1].should == 'ID'
rows[0][2].should == 'SIS User ID'
rows[0][3].should == 'SIS Login ID'
rows[0][4].should == 'Section'
rows[1][0].should == 'Muted assignments do not impact Current and Final score columns'
rows[1][5].should == 'Muted'
rows[1][6].should == ''
rows[2][2].should == ''
rows[2][3].should == ''
rows[2][4].should == ''
rows[2][-1].should == '(read only)'
rows[3][1].should == @user1.id.to_s
rows[3][2].should == 'SISUSERID'
rows[3][3].should == @user1.pseudonym.unique_id
rows[4][1].should == @user2.id.to_s
rows[4][2].should be_nil
rows[4][3].should == @user2.pseudonym.unique_id
rows[5][1].should == @user3.id.to_s
rows[5][2].should be_nil
rows[5][3].should be_nil
end
end
describe Course, "merge_into" do
it "should merge in another course" do
@c = Course.create!(:name => "some course")
@c.wiki.wiki_pages.length.should == 1
@c2 = Course.create!(:name => "another course")
g = @c2.assignment_groups.create!(:name => "some group")
due = Time.parse("Jan 1 2000 5:00pm")
@c2.assignments.create!(:title => "some assignment", :assignment_group => g, :due_at => due)
@c2.wiki.wiki_pages.create!(:title => "some page")
@c2.quizzes.create!(:title => "some quiz")
@c.assignments.length.should eql(0)
@c.merge_in(@c2, :everything => true)
@c.reload
@c.assignment_groups.length.should eql(1)
@c.assignment_groups.last.name.should eql(@c2.assignment_groups.last.name)
@c.assignment_groups.last.should_not eql(@c2.assignment_groups.last)
@c.assignments.length.should eql(1)
@c.assignments.last.title.should eql(@c2.assignments.last.title)
@c.assignments.last.should_not eql(@c2.assignments.last)
@c.assignments.last.due_at.should eql(@c2.assignments.last.due_at)
@c.wiki.wiki_pages.length.should eql(2)
@c.wiki.wiki_pages.map(&:title).include?(@c2.wiki.wiki_pages.last.title).should be_true
@c.wiki.wiki_pages.first.should_not eql(@c2.wiki.wiki_pages.last)
@c.wiki.wiki_pages.last.should_not eql(@c2.wiki.wiki_pages.last)
@c.quizzes.length.should eql(1)
@c.quizzes.last.title.should eql(@c2.quizzes.last.title)
@c.quizzes.last.should_not eql(@c2.quizzes.last)
end
it "should update due dates for date changes" do
new_start = Date.parse("Jun 1 2000")
new_end = Date.parse("Sep 1 2000")
@c = Course.create!(:name => "some course", :start_at => new_start, :conclude_at => new_end)
@c2 = Course.create!(:name => "another course", :start_at => Date.parse("Jan 1 2000"), :conclude_at => Date.parse("Mar 1 2000"))
g = @c2.assignment_groups.create!(:name => "some group")
@c2.assignments.create!(:title => "some assignment", :assignment_group => g, :due_at => Time.parse("Jan 3 2000 5:00pm"))
@c.assignments.length.should eql(0)
@c2.calendar_events.create!(:title => "some event", :start_at => Time.parse("Jan 11 2000 3:00pm"), :end_at => Time.parse("Jan 11 2000 4:00pm"))
@c.calendar_events.length.should eql(0)
@c.merge_in(@c2, :everything => true, :shift_dates => true)
@c.reload
@c.assignments.length.should eql(1)
@c.assignments.last.title.should eql(@c2.assignments.last.title)
@c.assignments.last.should_not eql(@c2.assignments.last)
@c.assignments.last.due_at.should > new_start
@c.assignments.last.due_at.should < new_end
@c.assignments.last.due_at.hour.should eql(@c2.assignments.last.due_at.hour)
@c.calendar_events.length.should eql(1)
@c.calendar_events.last.title.should eql(@c2.calendar_events.last.title)
@c.calendar_events.last.should_not eql(@c2.calendar_events.last)
@c.calendar_events.last.start_at.should > new_start
@c.calendar_events.last.start_at.should < new_end
@c.calendar_events.last.start_at.hour.should eql(@c2.calendar_events.last.start_at.hour)
@c.calendar_events.last.end_at.should > new_start
@c.calendar_events.last.end_at.should < new_end
@c.calendar_events.last.end_at.hour.should eql(@c2.calendar_events.last.end_at.hour)
end
it "should match times for changing due dates in a different time zone" do
Time.zone = "Mountain Time (US & Canada)"
new_start = Date.parse("Jun 1 2000")
new_end = Date.parse("Sep 1 2000")
@c = Course.create!(:name => "some course", :start_at => new_start, :conclude_at => new_end)
@c2 = Course.create!(:name => "another course", :start_at => Date.parse("Jan 1 2000"), :conclude_at => Date.parse("Mar 1 2000"))
g = @c2.assignment_groups.create!(:name => "some group")
@c2.assignments.create!(:title => "some assignment", :assignment_group => g, :due_at => Time.parse("Jan 3 2000 5:00pm"))
@c.assignments.length.should eql(0)
@c2.calendar_events.create!(:title => "some event", :start_at => Time.parse("Jan 11 2000 3:00pm"), :end_at => Time.parse("Jan 11 2000 4:00pm"))
@c.calendar_events.length.should eql(0)
@c.merge_in(@c2, :everything => true, :shift_dates => true)
@c.reload
@c.assignments.length.should eql(1)
@c.assignments.last.title.should eql(@c2.assignments.last.title)
@c.assignments.last.should_not eql(@c2.assignments.last)
@c.assignments.last.due_at.should > new_start
@c.assignments.last.due_at.should < new_end
@c.assignments.last.due_at.wday.should eql(@c2.assignments.last.due_at.wday)
@c.assignments.last.due_at.utc.hour.should eql(@c2.assignments.last.due_at.utc.hour)
@c.calendar_events.length.should eql(1)
@c.calendar_events.last.title.should eql(@c2.calendar_events.last.title)
@c.calendar_events.last.should_not eql(@c2.calendar_events.last)
@c.calendar_events.last.start_at.should > new_start
@c.calendar_events.last.start_at.should < new_end
@c.calendar_events.last.start_at.wday.should eql(@c2.calendar_events.last.start_at.wday)
@c.calendar_events.last.start_at.utc.hour.should eql(@c2.calendar_events.last.start_at.utc.hour)
@c.calendar_events.last.end_at.should > new_start
@c.calendar_events.last.end_at.should < new_end
@c.calendar_events.last.end_at.wday.should eql(@c2.calendar_events.last.end_at.wday)
@c.calendar_events.last.end_at.utc.hour.should eql(@c2.calendar_events.last.end_at.utc.hour)
Time.zone = nil
end
end
describe Course, "update_account_associations" do
it "should update account associations correctly" do
account1 = Account.create!(:name => 'first')
account2 = Account.create!(:name => 'second')
@c = Course.create!(:account => account1)
@c.associated_accounts.length.should eql(1)
@c.associated_accounts.first.should eql(account1)
@c.account = account2
@c.save!
@c.reload
@c.associated_accounts.length.should eql(1)
@c.associated_accounts.first.should eql(account2)
end
end
describe Course, "tabs_available" do
it "should return the defaults if nothing specified" do
course_with_teacher(:active_all => true)
length = Course.default_tabs.length
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should eql(Course.default_tabs.map{|t| t[:id] })
tab_ids.length.should eql(length)
end
it "should overwrite the order of tabs if configured" do
course_with_teacher(:active_all => true)
length = Course.default_tabs.length
@course.tab_configuration = [{'id' => Course::TAB_COLLABORATIONS}, {'id' => Course::TAB_CHAT}]
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should eql(([Course::TAB_COLLABORATIONS, Course::TAB_CHAT] + Course.default_tabs.map{|t| t[:id] }).uniq)
tab_ids.length.should eql(length)
end
it "should remove ids for tabs not in the default list" do
course_with_teacher(:active_all => true)
@course.tab_configuration = [{'id' => 912}]
@course.tabs_available(@user).map{|t| t[:id] }.should_not be_include(912)
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should eql(Course.default_tabs.map{|t| t[:id] })
tab_ids.length.should > 0
@course.tabs_available(@user).map{|t| t[:label] }.compact.length.should eql(tab_ids.length)
end
it "should hide unused tabs if not an admin" do
course_with_student(:active_all => true)
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should_not be_include(Course::TAB_SETTINGS)
tab_ids.length.should > 0
end
it "should show grades tab for students" do
course_with_student(:active_all => true)
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should be_include(Course::TAB_GRADES)
end
it "should not show grades tab for observers" do
course_with_student(:active_all => true)
@student = @user
user(:active_all => true)
@oe = @course.enroll_user(@user, 'ObserverEnrollment')
@oe.accept
@user.reload
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should_not be_include(Course::TAB_GRADES)
end
it "should show grades tab for observers if they are linked to a student" do
course_with_student(:active_all => true)
@student = @user
user(:active_all => true)
@oe = @course.enroll_user(@user, 'ObserverEnrollment')
@oe.accept
@oe.associated_user_id = @student.id
@oe.save!
@user.reload
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should be_include(Course::TAB_GRADES)
end
it "should show discussion tab for observers by default" do
course_with_observer
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should be_include(Course::TAB_DISCUSSIONS)
end
it "should not show discussion tab for observers without read_forum" do
course_with_observer
RoleOverride.create!(:context => @course.account, :permission => 'read_forum',
:enrollment_type => "ObserverEnrollment", :enabled => false)
tab_ids = @course.tabs_available(@user).map{|t| t[:id] }
tab_ids.should_not be_include(Course::TAB_DISCUSSIONS)
end
it "should include tabs for active external tools" do
course_with_student(:active_all => true)
tools = []
2.times do |n|
tools << @course.context_external_tools.create!(
:url => "http://example.com/ims/lti",
:consumer_key => "asdf",
:shared_secret => "hjkl",
:name => "external tool #{n+1}",
:course_navigation => {
:text => "blah",
:url => "http://example.com/ims/lti",
:default => false,
}
)
end
t1, t2 = tools
t2.workflow_state = "deleted"
t2.save!
tabs = @course.tabs_available.map { |tab| tab[:id] }
tabs.should be_include(t1.asset_string)
tabs.should_not be_include(t2.asset_string)
end
end
describe Course, "backup" do
it "should backup to a valid data structure" do
course_to_backup
data = @course.backup
data.should_not be_nil
data.length.should > 0
data.any?{|i| i.is_a?(Assignment)}.should eql(true)
data.any?{|i| i.is_a?(WikiPage)}.should eql(true)
data.any?{|i| i.is_a?(DiscussionTopic)}.should eql(true)
data.any?{|i| i.is_a?(CalendarEvent)}.should eql(true)
end
it "should backup to a valid json string" do
course_to_backup
data = @course.backup_to_json
data.should_not be_nil
data.length.should > 0
parse = JSON.parse(data) rescue nil
parse.should_not be_nil
parse.should be_is_a(Array)
parse.length.should > 0
end
context "merge_into_course" do
it "should merge implied content into another course" do
course_model
attachment_model
@old_attachment = @attachment
@old_topic = @course.discussion_topics.create!(:title => "some topic", :message => "<a href='/courses/#{@course.id}/files/#{@attachment.id}/download'>download this file</a>")
html = @old_topic.message
html.should match(Regexp.new("/courses/#{@course.id}/files/#{@attachment.id}/download"))
@old_course = @course
@new_course = course_model
@new_course.merge_into_course(@old_course, :all_topics => true)
@old_attachment.reload
@old_attachment.cloned_item_id.should_not be_nil
@new_attachment = @new_course.attachments.find_by_cloned_item_id(@old_attachment.cloned_item_id)
@new_attachment.should_not be_nil
@old_topic.reload
@old_topic.cloned_item_id.should_not be_nil
@new_topic = @new_course.discussion_topics.find_by_cloned_item_id(@old_topic.cloned_item_id)
@new_topic.should_not be_nil
html = @new_topic.message
html.should match(Regexp.new("/courses/#{@new_course.id}/files/#{@new_attachment.id}/download"))
end
it "should bring over linked files if not already brought over" do
course_model
attachment_model
@old_attachment = @attachment
@old_topic = @course.discussion_topics.create!(:title => "some topic", :message => "<a href='/courses/#{@course.id}/files/#{@attachment.id}/download'>download this file</a>")
html = @old_topic.message
html.should match(Regexp.new("/courses/#{@course.id}/files/#{@attachment.id}/download"))
@old_course = @course
@new_course = course_model
html = Course.migrate_content_links(@old_topic.message, @old_course, @new_course)
@old_attachment.reload
@old_attachment.cloned_item_id.should_not be_nil
@new_attachment = @new_course.attachments.find_by_cloned_item_id(@old_attachment.cloned_item_id)
@new_attachment.should_not be_nil
html.should match(Regexp.new("/courses/#{@new_course.id}/files/#{@new_attachment.id}/download"))
end
it "should bring over linked files that have been replaced" do
course_model
attachment_model
@orig_attachment = @attachment
@old_topic = @course.discussion_topics.create!(:title => "some topic", :message => "<a href='/courses/#{@course.id}/files/#{@attachment.id}/download'>download this file</a>")
html = @old_topic.message
html.should match(Regexp.new("/courses/#{@course.id}/files/#{@attachment.id}/download"))
@orig_attachment.destroy
attachment_model
@old_attachment = @attachment
@old_attachment.handle_duplicates(:overwrite)
@old_course = @course
@new_course = course_model
html = Course.migrate_content_links(@old_topic.message, @old_course, @new_course)
@old_attachment.reload
@old_attachment.cloned_item_id.should_not be_nil
@new_attachment = @new_course.attachments.find_by_cloned_item_id(@old_attachment.cloned_item_id)
@new_attachment.should_not be_nil
html.should match(Regexp.new("/courses/#{@new_course.id}/files/#{@new_attachment.id}/download"))
end
end
it "should not cross learning outcomes with learning outcome groups in the association" do
# set up two courses with two outcomes
course = course_model
default_group = LearningOutcomeGroup.default_for(course)
outcome = course.created_learning_outcomes.create!
default_group.add_item(outcome)
other_course = course_model
other_default_group = LearningOutcomeGroup.default_for(other_course)
other_outcome = other_course.created_learning_outcomes.create!
other_default_group.add_item(other_outcome)
# add another group to the first course, which "coincidentally" has the
# same id as the second course's outcome
other_group = course.learning_outcome_groups.build
other_group.id = other_outcome.id
other_group.save!
default_group.add_item(other_group)
# reload and check
course.reload
other_course.reload
course.learning_outcomes.should be_include(outcome)
course.learning_outcomes.should_not be_include(other_outcome)
other_course.learning_outcomes.should be_include(other_outcome)
end
it "should not count learning outcome groups as having outcomes" do
course = course_model
default_group = LearningOutcomeGroup.default_for(course)
other_group = course.learning_outcome_groups.create!
default_group.add_item(other_group)
course.has_outcomes.should == false
end
end
def course_to_backup
@course = course
group = @course.assignment_groups.create!(:name => "Some Assignment Group")
@course.assignments.create!(:title => "Some Assignment", :assignment_group => group)
@course.calendar_events.create!(:title => "Some Event", :start_at => Time.now, :end_at => Time.now)
@course.wiki.wiki_pages.create!(:title => "Some Page")
topic = @course.discussion_topics.create!(:title => "Some Discussion")
topic.discussion_entries.create!(:message => "just a test")
@course
end
describe Course, 'grade_publishing' do
before(:each) do
@course = Course.new
@course.root_account_id = Account.default.id
@course.save!
@course_section = @course.default_section
end
after(:each) do
Course.valid_grade_export_types.delete("test_export")
end
context 'mocked plugin settings' do
before(:each) do
@plugin_settings = Canvas::Plugin.find!("grade_export").default_settings.clone
@plugin = mock()
Canvas::Plugin.stubs("find!".to_sym).with('grade_export').returns(@plugin)
@plugin.stubs(:settings).returns{@plugin_settings}
end
context 'grade_publishing_status_translation' do
it 'should work with nil statuses and messages' do
@course.grade_publishing_status_translation(nil, nil).should == "Unpublished"
@course.grade_publishing_status_translation(nil, "hi").should == "Unpublished: hi"
@course.grade_publishing_status_translation("published", nil).should == "Published"
@course.grade_publishing_status_translation("published", "hi").should == "Published: hi"
end
it 'should work with invalid statuses' do
@course.grade_publishing_status_translation("invalid_status", nil).should == "Unknown status, invalid_status"
@course.grade_publishing_status_translation("invalid_status", "what what").should == "Unknown status, invalid_status: what what"
end
it "should work with empty string statuses and messages" do
@course.grade_publishing_status_translation("", "").should == "Unpublished"
@course.grade_publishing_status_translation("", "hi").should == "Unpublished: hi"
@course.grade_publishing_status_translation("published", "").should == "Published"
@course.grade_publishing_status_translation("published", "hi").should == "Published: hi"
end
it 'should work with all known statuses' do
@course.grade_publishing_status_translation("error", nil).should == "Error"
@course.grade_publishing_status_translation("error", "hi").should == "Error: hi"
@course.grade_publishing_status_translation("unpublished", nil).should == "Unpublished"
@course.grade_publishing_status_translation("unpublished", "hi").should == "Unpublished: hi"
@course.grade_publishing_status_translation("pending", nil).should == "Pending"
@course.grade_publishing_status_translation("pending", "hi").should == "Pending: hi"
@course.grade_publishing_status_translation("publishing", nil).should == "Publishing"
@course.grade_publishing_status_translation("publishing", "hi").should == "Publishing: hi"
@course.grade_publishing_status_translation("published", nil).should == "Published"
@course.grade_publishing_status_translation("published", "hi").should == "Published: hi"
@course.grade_publishing_status_translation("unpublishable", nil).should == "Unpublishable"
@course.grade_publishing_status_translation("unpublishable", "hi").should == "Unpublishable: hi"
end
end
def make_student_enrollments
@student_enrollments = []
9.times do
@student_enrollments << student_in_course({:course => @course, :active_all => true})
end
@student_enrollments[0].tap do |enrollment|
enrollment.grade_publishing_status = "published"
enrollment.save!
end
@student_enrollments[2].tap do |enrollment|
enrollment.grade_publishing_status = "unpublishable"
enrollment.save!
end
@student_enrollments[1].tap do |enrollment|
enrollment.grade_publishing_status = "error"
enrollment.grade_publishing_message = "cause of this reason"
enrollment.save!
end
@student_enrollments[3].tap do |enrollment|
enrollment.grade_publishing_status = "error"
enrollment.grade_publishing_message = "cause of that reason"
enrollment.save!
end
@student_enrollments[4].tap do |enrollment|
enrollment.grade_publishing_status = "unpublishable"
enrollment.save!
end
@student_enrollments[5].tap do |enrollment|
enrollment.grade_publishing_status = "unpublishable"
enrollment.save!
end
@student_enrollments[6].tap do |enrollment|
enrollment.workflow_state = "inactive"
enrollment.save!
end
@student_enrollments
end
def grade_publishing_user
@user = user_with_pseudonym
@pseudonym.account_id = @course.root_account_id
@pseudonym.sis_user_id = "U1"
@pseudonym.save!
@user
end
context 'grade_publishing_statuses' do
it 'should generate enrollments categorized by grade publishing message' do
make_student_enrollments
messages, overall_status = @course.grade_publishing_statuses
overall_status.should == "error"
messages.count.should == 5
messages["Unpublished"].sort_by(&:id).should == [
@student_enrollments[7],
@student_enrollments[8]
].sort_by(&:id)
messages["Published"].should == [
@student_enrollments[0]
]
messages["Error: cause of this reason"].should == [
@student_enrollments[1]
]
messages["Error: cause of that reason"].should == [
@student_enrollments[3]
]
messages["Unpublishable"].sort_by(&:id).should == [
@student_enrollments[2],
@student_enrollments[4],
@student_enrollments[5]
].sort_by(&:id)
end
it 'should correctly figure out the overall status with no enrollments' do
@course.grade_publishing_statuses.should == [{}, "unpublished"]
end
it 'should correctly figure out the overall status with invalid enrollment statuses' do
make_student_enrollments
@student_enrollments.each do |e|
e.grade_publishing_status = "invalid status"
e.save!
end
messages, overall_status = @course.grade_publishing_statuses
overall_status.should == "error"
messages.count.should == 3
messages["Unknown status, invalid status: cause of this reason"].should == [@student_enrollments[1]]
messages["Unknown status, invalid status: cause of that reason"].should == [@student_enrollments[3]]
messages["Unknown status, invalid status"].sort_by(&:id).should == [
@student_enrollments[0],
@student_enrollments[2],
@student_enrollments[4],
@student_enrollments[5],
@student_enrollments[7],
@student_enrollments[8]].sort_by(&:id)
end
it 'should fall back to the right overall status' do
make_student_enrollments
@student_enrollments.each do |e|
e.grade_publishing_status = "unpublishable"
e.grade_publishing_message = nil
e.save!
end
@course.reload.grade_publishing_statuses[1].should == "unpublishable"
@student_enrollments[0].tap do |e|
e.grade_publishing_status = "published"
e.save!
end
@course.reload.grade_publishing_statuses[1].should == "published"
@student_enrollments[1].tap do |e|
e.grade_publishing_status = "publishing"
e.save!
end
@course.reload.grade_publishing_statuses[1].should == "publishing"
@student_enrollments[2].tap do |e|
e.grade_publishing_status = "pending"
e.save!
end
@course.reload.grade_publishing_statuses[1].should == "pending"
@student_enrollments[3].tap do |e|
e.grade_publishing_status = "unpublished"
e.save!
end
@course.reload.grade_publishing_statuses[1].should == "unpublished"
@student_enrollments[4].tap do |e|
e.grade_publishing_status = "error"
e.save!
end
@course.reload.grade_publishing_statuses[1].should == "error"
end
end
context 'publish_final_grades' do
it 'should check whether or not grade export is enabled - success' do
grade_publishing_user
@course.expects(:send_final_grades_to_endpoint).with(@user).returns(nil)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings[:publish_endpoint] = "http://localhost/endpoint"
@course.publish_final_grades(@user)
end
it 'should check whether or not grade export is enabled - failure' do
grade_publishing_user
@plugin.stubs(:enabled?).returns(false)
@plugin_settings[:publish_endpoint] = "http://localhost/endpoint"
(lambda {@course.publish_final_grades(@user)}).should raise_error("final grade publishing disabled")
end
it 'should update all student enrollments with pending and a last update status' do
make_student_enrollments
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["published", "error", "unpublishable", "error", "unpublishable", "unpublishable", "unpublished", "unpublished", "unpublished"]
@student_enrollments.map(&:grade_publishing_message).should == [nil, "cause of this reason", nil, "cause of that reason", nil, nil, nil, nil, nil]
@student_enrollments.map(&:workflow_state).should == ["active"] * 6 + ["inactive"] + ["active"] * 2
@student_enrollments.map(&:last_publish_attempt_at).should == [nil] * 9
grade_publishing_user
@course.expects(:send_final_grades_to_endpoint).with(@user).returns(nil)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings[:publish_endpoint] = "http://localhost/endpoint"
@course.publish_final_grades(@user)
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["pending"] * 6 + ["unpublished"] + ["pending"] * 2
@student_enrollments.map(&:grade_publishing_message).should == [nil] * 9
@student_enrollments.map(&:workflow_state).should == ["active"] * 6 + ["inactive"] + ["active"] * 2
@student_enrollments.map(&:last_publish_attempt_at).each_with_index do |time, i|
if i == 6
time.should be_nil
else
time.should >= @course.created_at
end
end
end
it 'should kick off the actual grade send' do
grade_publishing_user
@course.expects(:send_later_if_production).with(:send_final_grades_to_endpoint, @user).returns(nil)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings[:publish_endpoint] = "http://localhost/endpoint"
@course.publish_final_grades(@user)
end
it 'should kick off the timeout when a success timeout is defined and waiting is configured' do
grade_publishing_user
@course.expects(:send_later_if_production).with(:send_final_grades_to_endpoint, @user).returns(nil)
current_time = Time.now.utc
Time.stubs(:now).returns(current_time)
current_time.stubs(:utc).returns(current_time)
@course.expects(:send_at).with(current_time + 1.seconds, :expire_pending_grade_publishing_statuses, current_time).returns(nil)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge!({
:publish_endpoint => "http://localhost/endpoint",
:success_timeout => "1",
:wait_for_success => "yes"
})
@course.publish_final_grades(@user)
end
it 'should not kick off the timeout when a success timeout is defined and waiting is not configured' do
grade_publishing_user
@course.expects(:send_later_if_production).with(:send_final_grades_to_endpoint, @user).returns(nil)
current_time = Time.now.utc
Time.stubs(:now).returns(current_time)
current_time.stubs(:utc).returns(current_time)
@course.expects(:send_at).times(0)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge!({
:publish_endpoint => "http://localhost/endpoint",
:success_timeout => "1",
:wait_for_success => "no"
})
@course.publish_final_grades(@user)
end
it 'should not kick off the timeout when a success timeout is not defined and waiting is not configured' do
grade_publishing_user
@course.expects(:send_later_if_production).with(:send_final_grades_to_endpoint, @user).returns(nil)
current_time = Time.now.utc
Time.stubs(:now).returns(current_time)
current_time.stubs(:utc).returns(current_time)
@course.expects(:send_at).times(0)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge!({
:publish_endpoint => "http://localhost/endpoint",
:success_timeout => "",
:wait_for_success => "no"
})
@course.publish_final_grades(@user)
end
it 'should not kick off the timeout when a success timeout is not defined and waiting is configured' do
grade_publishing_user
@course.expects(:send_later_if_production).with(:send_final_grades_to_endpoint, @user).returns(nil)
current_time = Time.now.utc
Time.stubs(:now).returns(current_time)
current_time.stubs(:utc).returns(current_time)
@course.expects(:send_at).times(0)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge!({
:publish_endpoint => "http://localhost/endpoint",
:success_timeout => "no",
:wait_for_success => "yes"
})
@course.publish_final_grades(@user)
end
end
context 'should_kick_off_grade_publishing_timeout?' do
it 'should cover all the necessary cases' do
@plugin_settings.merge! :success_timeout => "no", :wait_for_success => "yes"
@course.should_kick_off_grade_publishing_timeout?.should be_false
@plugin_settings.merge! :success_timeout => "", :wait_for_success => "no"
@course.should_kick_off_grade_publishing_timeout?.should be_false
@plugin_settings.merge! :success_timeout => "1", :wait_for_success => "no"
@course.should_kick_off_grade_publishing_timeout?.should be_false
@plugin_settings.merge! :success_timeout => "1", :wait_for_success => "yes"
@course.should_kick_off_grade_publishing_timeout?.should be_true
end
end
context 'valid_grade_export_types' do
it "should support instructure_csv" do
Course.valid_grade_export_types["instructure_csv"][:name].should == "Instructure formatted CSV"
course = mock()
enrollments = [mock(), mock()]
publishing_pseudonym = mock()
publishing_user = mock()
course.expects(:generate_grade_publishing_csv_output).with(enrollments, publishing_user, publishing_pseudonym).returns 42
Course.valid_grade_export_types["instructure_csv"][:callback].call(course,
enrollments, publishing_user, publishing_pseudonym).should == 42
Course.valid_grade_export_types["instructure_csv"][:requires_grading_standard].should be_false
Course.valid_grade_export_types["instructure_csv"][:requires_publishing_pseudonym].should be_false
end
end
context 'send_final_grades_to_endpoint' do
before { make_student_enrollments }
it "should clear the grade publishing message of unpublishable enrollments" do
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint", :format_type => "test_format"
grade_publishing_user
@ase = @student_enrollments.find_all{|e| e.workflow_state == 'active'}
Course.stubs(:valid_grade_export_types).returns({
"test_format" => {
:callback => lambda {|course, enrollments, publishing_user, publishing_pseudonym|
course.should == @course
enrollments.sort_by(&:id).should == @ase.sort_by(&:id)
publishing_pseudonym.should == @pseudonym
publishing_user.should == @user
return [
[[@ase[2].id, @ase[5].id],
"post1",
"test/mime1"],
[[@ase[4].id, @ase[7].id],
"post2",
"test/mime2"]]
}
}
})
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post1", "test/mime1")
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post2", "test/mime2")
@course.send_final_grades_to_endpoint @user
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["unpublishable", "unpublishable", "published", "unpublishable", "published", "published", "unpublished", "unpublishable", "published"]
@student_enrollments.map(&:grade_publishing_message).should == [nil] * 9
end
it "should try to publish appropriate enrollments" do
plugin_settings = Course.valid_grade_export_types["instructure_csv"]
Course.stubs(:valid_grade_export_types).returns(plugin_settings.merge({
"instructure_csv" => { :requires_grading_standard => true, :requires_publishing_pseudonym => true }}))
@course.grading_standard_enabled = true
@course.save!
grade_publishing_user
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge!({
:publish_endpoint => "http://localhost/endpoint",
:format_type => "instructure_csv"
})
@checked = false
Course.stubs(:valid_grade_export_types).returns({
"instructure_csv" => {
:callback => lambda {|course, enrollments, publishing_user, publishing_pseudonym|
course.should == @course
enrollments.sort_by(&:id).should == @student_enrollments.sort_by(&:id).find_all{|e| e.workflow_state == 'active'}
publishing_pseudonym.should == @pseudonym
publishing_user.should == @user
@checked = true
return []
}
}
})
@course.send_final_grades_to_endpoint @user
@checked.should be_true
end
it "should make sure grade publishing is enabled" do
@plugin.stubs(:enabled?).returns(false)
(lambda {@course.send_final_grades_to_endpoint nil}).should raise_error("final grade publishing disabled")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error"] * 6 + ["unpublished"] + ["error"] * 2
@student_enrollments.map(&:grade_publishing_message).should == ["final grade publishing disabled"] * 6 + [nil] + ["final grade publishing disabled"] * 2
end
it "should make sure an endpoint is defined" do
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => ""
(lambda {@course.send_final_grades_to_endpoint nil}).should raise_error("endpoint undefined")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error"] * 6 + ["unpublished"] + ["error"] * 2
@student_enrollments.map(&:grade_publishing_message).should == ["endpoint undefined"] * 6 + [nil] + ["endpoint undefined"] * 2
end
it "should make sure the publishing user can publish" do
plugin_settings = Course.valid_grade_export_types["instructure_csv"]
Course.stubs(:valid_grade_export_types).returns(plugin_settings.merge({
"instructure_csv" => { :requires_grading_standard => false, :requires_publishing_pseudonym => true }}))
@user = user
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint"
(lambda {@course.send_final_grades_to_endpoint @user}).should raise_error("publishing disallowed for this publishing user")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error"] * 6 + ["unpublished"] + ["error"] * 2
@student_enrollments.map(&:grade_publishing_message).should == ["publishing disallowed for this publishing user"] * 6 + [nil] + ["publishing disallowed for this publishing user"] * 2
end
it "should make sure there's a grading standard" do
plugin_settings = Course.valid_grade_export_types["instructure_csv"]
Course.stubs(:valid_grade_export_types).returns(plugin_settings.merge({
"instructure_csv" => { :requires_grading_standard => true, :requires_publishing_pseudonym => false }}))
@user = user
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint"
(lambda {@course.send_final_grades_to_endpoint @user}).should raise_error("grade publishing requires a grading standard")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error"] * 6 + ["unpublished"] + ["error"] * 2
@student_enrollments.map(&:grade_publishing_message).should == ["grade publishing requires a grading standard"] * 6 + [nil] + ["grade publishing requires a grading standard"] * 2
end
it "should make sure the format type is supported" do
grade_publishing_user
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint", :format_type => "invalid_Format"
(lambda {@course.send_final_grades_to_endpoint @user}).should raise_error("unknown format type: invalid_Format")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error"] * 6 + ["unpublished"] + ["error"] * 2
@student_enrollments.map(&:grade_publishing_message).should == ["unknown format type: invalid_Format"] * 6 + [nil] + ["unknown format type: invalid_Format"] * 2
end
def sample_grade_publishing_request(published_status)
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint", :format_type => "test_format"
grade_publishing_user
@ase = @student_enrollments.find_all{|e| e.workflow_state == 'active'}
Course.stubs(:valid_grade_export_types).returns({
"test_format" => {
:callback => lambda {|course, enrollments, publishing_user, publishing_pseudonym|
course.should == @course
enrollments.sort_by(&:id).should == @ase.sort_by(&:id)
publishing_pseudonym.should == @pseudonym
publishing_user.should == @user
return [
[[@ase[1].id, @ase[3].id],
"post1",
"test/mime1"],
[[@ase[4].id, @ase[7].id],
"post2",
"test/mime2"]]
}
}
})
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post1", "test/mime1")
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post2", "test/mime2")
@course.send_final_grades_to_endpoint @user
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["unpublishable", published_status, "unpublishable", published_status, published_status, "unpublishable", "unpublished", "unpublishable", published_status]
@student_enrollments.map(&:grade_publishing_message).should == [nil] * 9
end
it "should make callback's requested posts and mark requested enrollment ids ignored" do
sample_grade_publishing_request("published")
end
it "should recompute final grades" do
@course.expects(:recompute_student_scores_without_send_later)
sample_grade_publishing_request("published")
end
it "should not set the status to publishing if a timeout didn't kick off - timeout, wait" do
@plugin_settings.merge! :success_timeout => "1", :wait_for_success => "yes"
sample_grade_publishing_request("publishing")
end
it "should not set the status to publishing if a timeout didn't kick off - timeout, no wait" do
@plugin_settings.merge! :success_timeout => "2", :wait_for_success => "false"
sample_grade_publishing_request("published")
end
it "should not set the status to publishing if a timeout didn't kick off - no timeout, wait" do
@plugin_settings.merge! :success_timeout => "no", :wait_for_success => "yes"
sample_grade_publishing_request("published")
end
it "should not set the status to publishing if a timeout didn't kick off - no timeout, no wait" do
@plugin_settings.merge! :success_timeout => "false", :wait_for_success => "no"
sample_grade_publishing_request("published")
end
it "should try and make all posts even if one of the postings fails" do
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint", :format_type => "test_format"
grade_publishing_user
@ase = @student_enrollments.find_all{|e| e.workflow_state == 'active'}
Course.stubs(:valid_grade_export_types).returns({
"test_format" => {
:callback => lambda {|course, enrollments, publishing_user, publishing_pseudonym|
course.should == @course
enrollments.sort_by(&:id).should == @ase.sort_by(&:id)
publishing_pseudonym.should == @pseudonym
publishing_user.should == @user
return [
[[@ase[1].id, @ase[3].id],
"post1",
"test/mime1"],
[[@ase[4].id, @ase[7].id],
"post2",
"test/mime2"],
[[@ase[2].id, @ase[0].id],
"post3",
"test/mime3"]]
}
}
})
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post1", "test/mime1")
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post2", "test/mime2").raises("waaah fail")
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post3", "test/mime3")
(lambda {@course.send_final_grades_to_endpoint(@user)}).should raise_error("waaah fail")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["published", "published", "published", "published", "error", "unpublishable", "unpublished", "unpublishable", "error"]
@student_enrollments.map(&:grade_publishing_message).should == [nil] * 4 + ["waaah fail"] + [nil] * 3 + ["waaah fail"]
end
it "should try and make all posts even if two of the postings fail" do
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint", :format_type => "test_format"
grade_publishing_user
@ase = @student_enrollments.find_all{|e| e.workflow_state == 'active'}
Course.stubs(:valid_grade_export_types).returns({
"test_format" => {
:callback => lambda {|course, enrollments, publishing_user, publishing_pseudonym|
course.should == @course
enrollments.sort_by(&:id).should == @ase.sort_by(&:id)
publishing_pseudonym.should == @pseudonym
publishing_user.should == @user
return [
[[@ase[1].id, @ase[3].id],
"post1",
"test/mime1"],
[[@ase[4].id, @ase[7].id],
"post2",
"test/mime2"],
[[@ase[2].id, @ase[0].id],
"post3",
"test/mime3"]]
}
}
})
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post1", "test/mime1").raises("waaah fail")
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post2", "test/mime2").raises("waaah fail")
SSLCommon.expects(:post_data).with("http://localhost/endpoint", "post3", "test/mime3")
(lambda {@course.send_final_grades_to_endpoint(@user)}).should raise_error("waaah fail")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["published", "error", "published", "error", "error", "unpublishable", "unpublished", "unpublishable", "error"]
@student_enrollments.map(&:grade_publishing_message).should == [nil, "waaah fail", nil, "waaah fail", "waaah fail", nil, nil, nil, "waaah fail"]
end
it "should fail gracefully when the posting generator fails" do
@plugin.stubs(:enabled?).returns(true)
@plugin_settings.merge! :publish_endpoint => "http://localhost/endpoint", :format_type => "test_format"
grade_publishing_user
@ase = @student_enrollments.find_all{|e| e.workflow_state == 'active'}
Course.stubs(:valid_grade_export_types).returns({
"test_format" => {
:callback => lambda {|course, enrollments, publishiing_user, publishing_pseudonym|
raise "waaah fail"
}
}
})
(lambda {@course.send_final_grades_to_endpoint(@user)}).should raise_error("waaah fail")
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error", "error", "error", "error", "error", "error", "unpublished", "error", "error"]
@student_enrollments.map(&:grade_publishing_message).should == ["waaah fail"] * 6 + [nil] + ["waaah fail"] * 2
end
end
context 'generate_grade_publishing_csv_output' do
def add_pseudonym(enrollment, account, unique_id, sis_user_id)
pseudonym = account.pseudonyms.build
pseudonym.user = enrollment.user
pseudonym.unique_id = unique_id
pseudonym.sis_user_id = sis_user_id
pseudonym.save!
end
it 'should generate valid csv without a grading standard' do
make_student_enrollments
grade_publishing_user
@course.assignment_groups.create(:name => "Assignments")
a1 = @course.assignments.create!(:title => "A1", :points_possible => 10)
a2 = @course.assignments.create!(:title => "A2", :points_possible => 10)
@course.enroll_teacher(@user).tap{|e| e.workflow_state = 'active'; e.save!}
@ase = @student_enrollments.find_all(&:active?)
a1.grade_student(@ase[0].user, { :grade => "9", :grader => @user })
a2.grade_student(@ase[0].user, { :grade => "10", :grader => @user })
a1.grade_student(@ase[1].user, { :grade => "6", :grader => @user })
a2.grade_student(@ase[1].user, { :grade => "7", :grader => @user })
a1.grade_student(@ase[7].user, { :grade => "8", :grader => @user })
a2.grade_student(@ase[7].user, { :grade => "9", :grader => @user })
add_pseudonym(@ase[2], Account.default, "student2", nil)
add_pseudonym(@ase[3], Account.default, "student3", "student3")
add_pseudonym(@ase[4], Account.default, "student4a", "student4a")
add_pseudonym(@ase[4], Account.default, "student4b", "student4b")
another_account = account_model
add_pseudonym(@ase[5], another_account, "student5", nil)
add_pseudonym(@ase[6], another_account, "student6", "student6")
add_pseudonym(@ase[7], Account.default, "student7a", "student7a")
add_pseudonym(@ase[7], Account.default, "student7b", "student7b")
@course.recompute_student_scores_without_send_later
@course.generate_grade_publishing_csv_output(@ase.map(&:reload), @user, @pseudonym).should == [
[@ase.map(&:id),
("publisher_id,publisher_sis_id,section_id,section_sis_id," +
"student_id,student_sis_id,enrollment_id,enrollment_status," +
"score\n" +
"#{@user.id},U1,#{@ase[0].course_section_id},,#{@ase[0].user.id},,#{@ase[0].id},active,95\n" +
"#{@user.id},U1,#{@ase[1].course_section_id},,#{@ase[1].user.id},,#{@ase[1].id},active,65\n" +
"#{@user.id},U1,#{@ase[2].course_section_id},,#{@ase[2].user.id},,#{@ase[2].id},active,0\n" +
"#{@user.id},U1,#{@ase[3].course_section_id},,#{@ase[3].user.id},student3,#{@ase[3].id},active,0\n" +
"#{@user.id},U1,#{@ase[4].course_section_id},,#{@ase[4].user.id},student4a,#{@ase[4].id},active,0\n" +
"#{@user.id},U1,#{@ase[4].course_section_id},,#{@ase[4].user.id},student4b,#{@ase[4].id},active,0\n" +
"#{@user.id},U1,#{@ase[5].course_section_id},,#{@ase[5].user.id},,#{@ase[5].id},active,0\n" +
"#{@user.id},U1,#{@ase[6].course_section_id},,#{@ase[6].user.id},,#{@ase[6].id},active,0\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7a,#{@ase[7].id},active,85\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7b,#{@ase[7].id},active,85\n"),
"text/csv"]
]
end
it 'should generate valid csv without a publishing pseudonym' do
make_student_enrollments
@user = user_model
@course.assignment_groups.create(:name => "Assignments")
a1 = @course.assignments.create!(:title => "A1", :points_possible => 10)
a2 = @course.assignments.create!(:title => "A2", :points_possible => 10)
@course.enroll_teacher(@user).tap{|e| e.workflow_state = 'active'; e.save!}
@ase = @student_enrollments.find_all(&:active?)
a1.grade_student(@ase[0].user, { :grade => "9", :grader => @user })
a2.grade_student(@ase[0].user, { :grade => "10", :grader => @user })
a1.grade_student(@ase[1].user, { :grade => "6", :grader => @user })
a2.grade_student(@ase[1].user, { :grade => "7", :grader => @user })
a1.grade_student(@ase[7].user, { :grade => "8", :grader => @user })
a2.grade_student(@ase[7].user, { :grade => "9", :grader => @user })
add_pseudonym(@ase[2], Account.default, "student2", nil)
add_pseudonym(@ase[3], Account.default, "student3", "student3")
add_pseudonym(@ase[4], Account.default, "student4a", "student4a")
add_pseudonym(@ase[4], Account.default, "student4b", "student4b")
another_account = account_model
add_pseudonym(@ase[5], another_account, "student5", nil)
add_pseudonym(@ase[6], another_account, "student6", "student6")
add_pseudonym(@ase[7], Account.default, "student7a", "student7a")
add_pseudonym(@ase[7], Account.default, "student7b", "student7b")
@course.recompute_student_scores_without_send_later
@course.generate_grade_publishing_csv_output(@ase.map(&:reload), @user, @pseudonym).should == [
[@ase.map(&:id),
("publisher_id,publisher_sis_id,section_id,section_sis_id," +
"student_id,student_sis_id,enrollment_id,enrollment_status," +
"score\n" +
"#{@user.id},,#{@ase[0].course_section_id},,#{@ase[0].user.id},,#{@ase[0].id},active,95\n" +
"#{@user.id},,#{@ase[1].course_section_id},,#{@ase[1].user.id},,#{@ase[1].id},active,65\n" +
"#{@user.id},,#{@ase[2].course_section_id},,#{@ase[2].user.id},,#{@ase[2].id},active,0\n" +
"#{@user.id},,#{@ase[3].course_section_id},,#{@ase[3].user.id},student3,#{@ase[3].id},active,0\n" +
"#{@user.id},,#{@ase[4].course_section_id},,#{@ase[4].user.id},student4a,#{@ase[4].id},active,0\n" +
"#{@user.id},,#{@ase[4].course_section_id},,#{@ase[4].user.id},student4b,#{@ase[4].id},active,0\n" +
"#{@user.id},,#{@ase[5].course_section_id},,#{@ase[5].user.id},,#{@ase[5].id},active,0\n" +
"#{@user.id},,#{@ase[6].course_section_id},,#{@ase[6].user.id},,#{@ase[6].id},active,0\n" +
"#{@user.id},,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7a,#{@ase[7].id},active,85\n" +
"#{@user.id},,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7b,#{@ase[7].id},active,85\n"),
"text/csv"]
]
end
it 'should generate valid csv with a section id' do
@course_section.sis_source_id = "section1"
@course_section.save!
make_student_enrollments
grade_publishing_user
@course.assignment_groups.create(:name => "Assignments")
a1 = @course.assignments.create!(:title => "A1", :points_possible => 10)
a2 = @course.assignments.create!(:title => "A2", :points_possible => 10)
@course.enroll_teacher(@user).tap{|e| e.workflow_state = 'active'; e.save!}
@ase = @student_enrollments.find_all(&:active?)
a1.grade_student(@ase[0].user, { :grade => "9", :grader => @user })
a2.grade_student(@ase[0].user, { :grade => "10", :grader => @user })
a1.grade_student(@ase[1].user, { :grade => "6", :grader => @user })
a2.grade_student(@ase[1].user, { :grade => "7", :grader => @user })
a1.grade_student(@ase[7].user, { :grade => "8", :grader => @user })
a2.grade_student(@ase[7].user, { :grade => "9", :grader => @user })
add_pseudonym(@ase[2], Account.default, "student2", nil)
add_pseudonym(@ase[3], Account.default, "student3", "student3")
add_pseudonym(@ase[4], Account.default, "student4a", "student4a")
add_pseudonym(@ase[4], Account.default, "student4b", "student4b")
another_account = account_model
add_pseudonym(@ase[5], another_account, "student5", nil)
add_pseudonym(@ase[6], another_account, "student6", "student6")
add_pseudonym(@ase[7], Account.default, "student7a", "student7a")
add_pseudonym(@ase[7], Account.default, "student7b", "student7b")
@course.recompute_student_scores_without_send_later
@course.generate_grade_publishing_csv_output(@ase.map(&:reload), @user, @pseudonym).should == [
[@ase.map(&:id),
("publisher_id,publisher_sis_id,section_id,section_sis_id," +
"student_id,student_sis_id,enrollment_id,enrollment_status," +
"score\n" +
"#{@user.id},U1,#{@ase[0].course_section_id},section1,#{@ase[0].user.id},,#{@ase[0].id},active,95\n" +
"#{@user.id},U1,#{@ase[1].course_section_id},section1,#{@ase[1].user.id},,#{@ase[1].id},active,65\n" +
"#{@user.id},U1,#{@ase[2].course_section_id},section1,#{@ase[2].user.id},,#{@ase[2].id},active,0\n" +
"#{@user.id},U1,#{@ase[3].course_section_id},section1,#{@ase[3].user.id},student3,#{@ase[3].id},active,0\n" +
"#{@user.id},U1,#{@ase[4].course_section_id},section1,#{@ase[4].user.id},student4a,#{@ase[4].id},active,0\n" +
"#{@user.id},U1,#{@ase[4].course_section_id},section1,#{@ase[4].user.id},student4b,#{@ase[4].id},active,0\n" +
"#{@user.id},U1,#{@ase[5].course_section_id},section1,#{@ase[5].user.id},,#{@ase[5].id},active,0\n" +
"#{@user.id},U1,#{@ase[6].course_section_id},section1,#{@ase[6].user.id},,#{@ase[6].id},active,0\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},section1,#{@ase[7].user.id},student7a,#{@ase[7].id},active,85\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},section1,#{@ase[7].user.id},student7b,#{@ase[7].id},active,85\n"),
"text/csv"]
]
end
it 'should generate valid csv with a grading standard' do
make_student_enrollments
grade_publishing_user
@course.assignment_groups.create(:name => "Assignments")
@course.grading_standard_id = 0
@course.save!
a1 = @course.assignments.create!(:title => "A1", :points_possible => 10)
a2 = @course.assignments.create!(:title => "A2", :points_possible => 10)
@course.enroll_teacher(@user).tap{|e| e.workflow_state = 'active'; e.save!}
@ase = @student_enrollments.find_all(&:active?)
a1.grade_student(@ase[0].user, { :grade => "9", :grader => @user })
a2.grade_student(@ase[0].user, { :grade => "10", :grader => @user })
a1.grade_student(@ase[1].user, { :grade => "6", :grader => @user })
a2.grade_student(@ase[1].user, { :grade => "7", :grader => @user })
a1.grade_student(@ase[7].user, { :grade => "8", :grader => @user })
a2.grade_student(@ase[7].user, { :grade => "9", :grader => @user })
add_pseudonym(@ase[2], Account.default, "student2", nil)
add_pseudonym(@ase[3], Account.default, "student3", "student3")
add_pseudonym(@ase[4], Account.default, "student4a", "student4a")
add_pseudonym(@ase[4], Account.default, "student4b", "student4b")
another_account = account_model
add_pseudonym(@ase[5], another_account, "student5", nil)
add_pseudonym(@ase[6], another_account, "student6", "student6")
add_pseudonym(@ase[7], Account.default, "student7a", "student7a")
add_pseudonym(@ase[7], Account.default, "student7b", "student7b")
@course.recompute_student_scores_without_send_later
@course.generate_grade_publishing_csv_output(@ase.map(&:reload), @user, @pseudonym).should == [
[@ase.map(&:id),
("publisher_id,publisher_sis_id,section_id,section_sis_id," +
"student_id,student_sis_id,enrollment_id,enrollment_status," +
"score,grade\n" +
"#{@user.id},U1,#{@ase[0].course_section_id},,#{@ase[0].user.id},,#{@ase[0].id},active,95,A\n" +
"#{@user.id},U1,#{@ase[1].course_section_id},,#{@ase[1].user.id},,#{@ase[1].id},active,65,D\n" +
"#{@user.id},U1,#{@ase[2].course_section_id},,#{@ase[2].user.id},,#{@ase[2].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[3].course_section_id},,#{@ase[3].user.id},student3,#{@ase[3].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[4].course_section_id},,#{@ase[4].user.id},student4a,#{@ase[4].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[4].course_section_id},,#{@ase[4].user.id},student4b,#{@ase[4].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[5].course_section_id},,#{@ase[5].user.id},,#{@ase[5].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[6].course_section_id},,#{@ase[6].user.id},,#{@ase[6].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7a,#{@ase[7].id},active,85,B\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7b,#{@ase[7].id},active,85,B\n"),
"text/csv"]
]
end
it 'should generate valid csv and skip users with no computed final score' do
make_student_enrollments
grade_publishing_user
@course.assignment_groups.create(:name => "Assignments")
@course.grading_standard_id = 0
@course.save!
a1 = @course.assignments.create!(:title => "A1", :points_possible => 10)
a2 = @course.assignments.create!(:title => "A2", :points_possible => 10)
@course.enroll_teacher(@user).tap{|e| e.workflow_state = 'active'; e.save!}
@ase = @student_enrollments.find_all(&:active?)
a1.grade_student(@ase[0].user, { :grade => "9", :grader => @user })
a2.grade_student(@ase[0].user, { :grade => "10", :grader => @user })
a1.grade_student(@ase[1].user, { :grade => "6", :grader => @user })
a2.grade_student(@ase[1].user, { :grade => "7", :grader => @user })
a1.grade_student(@ase[7].user, { :grade => "8", :grader => @user })
a2.grade_student(@ase[7].user, { :grade => "9", :grader => @user })
add_pseudonym(@ase[2], Account.default, "student2", nil)
add_pseudonym(@ase[3], Account.default, "student3", "student3")
add_pseudonym(@ase[4], Account.default, "student4a", "student4a")
add_pseudonym(@ase[4], Account.default, "student4b", "student4b")
another_account = account_model
add_pseudonym(@ase[5], another_account, "student5", nil)
add_pseudonym(@ase[6], another_account, "student6", "student6")
add_pseudonym(@ase[7], Account.default, "student7a", "student7a")
add_pseudonym(@ase[7], Account.default, "student7b", "student7b")
@course.recompute_student_scores_without_send_later
@ase.map(&:reload)
@ase[1].computed_final_score = nil
@ase[3].computed_final_score = nil
@ase[4].computed_final_score = nil
@course.generate_grade_publishing_csv_output(@ase, @user, @pseudonym).should == [
[@ase.map(&:id) - [@ase[1].id, @ase[3].id, @ase[4].id],
("publisher_id,publisher_sis_id,section_id,section_sis_id," +
"student_id,student_sis_id,enrollment_id,enrollment_status," +
"score,grade\n" +
"#{@user.id},U1,#{@ase[0].course_section_id},,#{@ase[0].user.id},,#{@ase[0].id},active,95,A\n" +
"#{@user.id},U1,#{@ase[2].course_section_id},,#{@ase[2].user.id},,#{@ase[2].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[5].course_section_id},,#{@ase[5].user.id},,#{@ase[5].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[6].course_section_id},,#{@ase[6].user.id},,#{@ase[6].id},active,0,F\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7a,#{@ase[7].id},active,85,B\n" +
"#{@user.id},U1,#{@ase[7].course_section_id},,#{@ase[7].user.id},student7b,#{@ase[7].id},active,85,B\n"),
"text/csv"]
]
end
end
context 'expire_pending_grade_publishing_statuses' do
it 'should update the right enrollments' do
make_student_enrollments
first_time = Time.now.utc
second_time = first_time + 2.seconds
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["published", "error", "unpublishable", "error", "unpublishable", "unpublishable", "unpublished", "unpublished", "unpublished"]
@student_enrollments[0].grade_publishing_status = "pending"
@student_enrollments[0].last_publish_attempt_at = first_time
@student_enrollments[1].grade_publishing_status = "publishing"
@student_enrollments[1].last_publish_attempt_at = first_time
@student_enrollments[2].grade_publishing_status = "pending"
@student_enrollments[2].last_publish_attempt_at = second_time
@student_enrollments[3].grade_publishing_status = "publishing"
@student_enrollments[3].last_publish_attempt_at = second_time
@student_enrollments[4].grade_publishing_status = "published"
@student_enrollments[4].last_publish_attempt_at = first_time
@student_enrollments[5].grade_publishing_status = "unpublished"
@student_enrollments[5].last_publish_attempt_at = first_time
@student_enrollments.map(&:save)
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["pending", "publishing", "pending", "publishing", "published", "unpublished", "unpublished", "unpublished", "unpublished"]
@course.expire_pending_grade_publishing_statuses(first_time)
@student_enrollments.map(&:reload).map(&:grade_publishing_status).should == ["error", "error", "pending", "publishing", "published", "unpublished", "unpublished", "unpublished", "unpublished"]
end
end
context 'grading_standard_enabled' do
it 'should work for a number of boolean representations' do
@course.grading_standard_enabled?.should be_false
@course.grading_standard_enabled.should be_false
[[false, false], [true, true], ["false", false], ["true", true],
["0", false], [0, false], ["1", true], [1, true], ["off", false],
["on", true], ["yes", true], ["no", false]].each do |val, enabled|
@course.grading_standard_enabled = val
@course.grading_standard_enabled?.should == enabled
@course.grading_standard_enabled.should == enabled
@course.grading_standard_id.should be_nil unless enabled
@course.grading_standard_id.should_not be_nil if enabled
@course.bool_res(val).should == enabled
end
end
end
end
context 'integration suite' do
def quick_sanity_check(user)
Course.valid_grade_export_types["test_export"] = {
:name => "test export",
:callback => lambda {|course, enrollments, publishing_user, publishing_pseudonym|
course.should == @course
publishing_pseudonym.should == @pseudonym
publishing_user.should == @user
return [[[], "test-jt-data", "application/jtmimetype"]]
},
:requires_grading_standard => false, :requires_publishing_pseudonym => true}
server, server_thread, post_lines = start_test_http_server
@plugin = Canvas::Plugin.find!('grade_export')
@ps = PluginSetting.new(:name => @plugin.id, :settings => @plugin.default_settings)
@ps.posted_settings = @plugin.default_settings.merge({
:format_type => "test_export",
:wait_for_success => "no",
:publish_endpoint => "http://localhost:#{server.addr[1]}/endpoint"
})
@ps.save!
@course.grading_standard_id = 0
@course.publish_final_grades(user)
server_thread.join
post_lines.should == [
"POST /endpoint HTTP/1.1",
"Accept: */*",
"Content-Type: application/jtmimetype",
"",
"test-jt-data"]
end
it 'should pass a quick sanity check' do
@user = user_with_pseudonym
@pseudonym.account_id = @course.root_account_id
@pseudonym.sis_user_id = "U1"
@pseudonym.save!
quick_sanity_check(@user)
end
it 'should not allow grade publishing for a user that is disallowed' do
@user = User.new
(lambda { quick_sanity_check(@user) }).should raise_error("publishing disallowed for this publishing user")
end
it 'should not allow grade publishing for a user with a pseudonym in the wrong account' do
@user = user_with_pseudonym
@pseudonym.account = account_model
@pseudonym.sis_user_id = "U1"
@pseudonym.save!
(lambda { quick_sanity_check(@user) }).should raise_error("publishing disallowed for this publishing user")
end
it 'should not allow grade publishing for a user with a pseudonym without a sis id' do
@user = user_with_pseudonym
@pseudonym.account_id = @course.root_account_id
@pseudonym.sis_user_id = nil
@pseudonym.save!
(lambda { quick_sanity_check(@user) }).should raise_error("publishing disallowed for this publishing user")
end
it 'should publish csv' do
@user = user_with_pseudonym
@pseudonym.sis_user_id = "U1"
@pseudonym.account_id = @course.root_account_id
@pseudonym.save!
server, server_thread, post_lines = start_test_http_server
@plugin = Canvas::Plugin.find!('grade_export')
@ps = PluginSetting.new(:name => @plugin.id, :settings => @plugin.default_settings)
@ps.posted_settings = @plugin.default_settings.merge({
:format_type => "instructure_csv",
:wait_for_success => "no",
:publish_endpoint => "http://localhost:#{server.addr[1]}/endpoint"
})
@ps.save!
@course.grading_standard_id = 0
@course.publish_final_grades(@user)
server_thread.join
post_lines.should == [
"POST /endpoint HTTP/1.1",
"Accept: */*",
"Content-Type: text/csv",
"",
"publisher_id,publisher_sis_id,section_id,section_sis_id,student_id," +
"student_sis_id,enrollment_id,enrollment_status,score,grade\n"]
end
it 'should publish grades' do
process_csv_data_cleanly(
"user_id,login_id,password,first_name,last_name,email,status",
"T1,Teacher1,,T,1,t1@example.com,active",
"S1,Student1,,S,1,s1@example.com,active",
"S2,Student2,,S,2,s2@example.com,active",
"S3,Student3,,S,3,s3@example.com,active",
"S4,Student4,,S,4,s4@example.com,active",
"S5,Student5,,S,5,s5@example.com,active",
"S6,Student6,,S,6,s6@example.com,active")
process_csv_data_cleanly(
"course_id,short_name,long_name,account_id,term_id,status",
"C1,C1,C1,,,active")
@course = Course.find_by_sis_source_id("C1")
@course.assignment_groups.create(:name => "Assignments")
process_csv_data_cleanly(
"section_id,course_id,name,status,start_date,end_date",
"S1,C1,S1,active,,",
"S2,C1,S2,active,,",
"S3,C1,S3,active,,",
"S4,C1,S4,active,,")
process_csv_data_cleanly(
"course_id,user_id,role,section_id,status",
",T1,teacher,S1,active",
",S1,student,S1,active",
",S2,student,S2,active",
",S3,student,S2,active",
",S4,student,S1,active",
",S5,student,S3,active",
",S6,student,S4,active")
a1 = @course.assignments.create!(:title => "A1", :points_possible => 10)
a2 = @course.assignments.create!(:title => "A2", :points_possible => 10)
def getpseudonym(user_sis_id)
pseudo = Pseudonym.find_by_sis_user_id(user_sis_id)
pseudo.should_not be_nil
pseudo
end
def getuser(user_sis_id)
user = getpseudonym(user_sis_id).user
user.should_not be_nil
user
end
def getsection(section_sis_id)
section = CourseSection.find_by_sis_source_id(section_sis_id)
section.should_not be_nil
section
end
def getenroll(user_sis_id, section_sis_id)
e = Enrollment.find_by_user_id_and_course_section_id(getuser(user_sis_id).id, getsection(section_sis_id).id)
e.should_not be_nil
e
end
a1.grade_student(getuser("S1"), { :grade => "6", :grader => getuser("T1") })
a1.grade_student(getuser("S2"), { :grade => "6", :grader => getuser("T1") })
a1.grade_student(getuser("S3"), { :grade => "7", :grader => getuser("T1") })
a1.grade_student(getuser("S5"), { :grade => "7", :grader => getuser("T1") })
a1.grade_student(getuser("S6"), { :grade => "8", :grader => getuser("T1") })
a2.grade_student(getuser("S1"), { :grade => "8", :grader => getuser("T1") })
a2.grade_student(getuser("S2"), { :grade => "9", :grader => getuser("T1") })
a2.grade_student(getuser("S3"), { :grade => "9", :grader => getuser("T1") })
a2.grade_student(getuser("S5"), { :grade => "10", :grader => getuser("T1") })
a2.grade_student(getuser("S6"), { :grade => "10", :grader => getuser("T1") })
stud5, stud6, sec4 = nil, nil, nil
Pseudonym.find_by_sis_user_id("S5").tap do |p|
stud5 = p
p.sis_user_id = nil
p.save
end
Pseudonym.find_by_sis_user_id("S6").tap do |p|
stud6 = p
p.sis_user_id = nil
p.save
end
getsection("S4").tap do |s|
sec4 = s
sec4id = s.sis_source_id
s.save
end
GradeCalculator.recompute_final_score(["S1", "S2", "S3", "S4"].map{|x|getuser(x).id}, @course.id)
@course.reload
teacher = Pseudonym.find_by_sis_user_id("T1")
teacher.should_not be_nil
server, server_thread, post_lines = start_test_http_server
@plugin = Canvas::Plugin.find!('grade_export')
@ps = PluginSetting.new(:name => @plugin.id, :settings => @plugin.default_settings)
@ps.posted_settings = @plugin.default_settings.merge({
:format_type => "instructure_csv",
:wait_for_success => "no",
:publish_endpoint => "http://localhost:#{server.addr[1]}/endpoint"
})
@ps.save!
@course.publish_final_grades(teacher.user)
server_thread.join
post_lines.should == [
"POST /endpoint HTTP/1.1",
"Accept: */*",
"Content-Type: text/csv",
"",
"publisher_id,publisher_sis_id,section_id,section_sis_id,student_id," +
"student_sis_id,enrollment_id,enrollment_status,score\n" +
"#{teacher.user.id},T1,#{getsection("S1").id},S1,#{getpseudonym("S1").user.id},S1,#{getenroll("S1", "S1").id},active,70\n" +
"#{teacher.user.id},T1,#{getsection("S2").id},S2,#{getpseudonym("S2").user.id},S2,#{getenroll("S2", "S2").id},active,75\n" +
"#{teacher.user.id},T1,#{getsection("S2").id},S2,#{getpseudonym("S3").user.id},S3,#{getenroll("S3", "S2").id},active,80\n" +
"#{teacher.user.id},T1,#{getsection("S1").id},S1,#{getpseudonym("S4").user.id},S4,#{getenroll("S4", "S1").id},active,0\n" +
"#{teacher.user.id},T1,#{getsection("S3").id},S3,#{stud5.user.id},,#{Enrollment.find_by_user_id_and_course_section_id(stud5.user.id, getsection("S3").id).id},active,85\n" +
"#{teacher.user.id},T1,#{sec4.id},S4,#{stud6.user.id},,#{Enrollment.find_by_user_id_and_course_section_id(stud6.user.id, sec4.id).id},active,90\n"]
@course.grading_standard_id = 0
@course.save
server, server_thread, post_lines = start_test_http_server
@ps.posted_settings = @plugin.default_settings.merge({
:publish_endpoint => "http://localhost:#{server.addr[1]}/endpoint"
})
@ps.save!
@course.publish_final_grades(teacher.user)
server_thread.join
post_lines.should == [
"POST /endpoint HTTP/1.1",
"Accept: */*",
"Content-Type: text/csv",
"",
"publisher_id,publisher_sis_id,section_id,section_sis_id,student_id," +
"student_sis_id,enrollment_id,enrollment_status,score,grade\n" +
"#{teacher.user.id},T1,#{getsection("S1").id},S1,#{getpseudonym("S1").user.id},S1,#{getenroll("S1", "S1").id},active,70,C-\n" +
"#{teacher.user.id},T1,#{getsection("S2").id},S2,#{getpseudonym("S2").user.id},S2,#{getenroll("S2", "S2").id},active,75,C\n" +
"#{teacher.user.id},T1,#{getsection("S2").id},S2,#{getpseudonym("S3").user.id},S3,#{getenroll("S3", "S2").id},active,80,B-\n" +
"#{teacher.user.id},T1,#{getsection("S1").id},S1,#{getpseudonym("S4").user.id},S4,#{getenroll("S4", "S1").id},active,0,F\n" +
"#{teacher.user.id},T1,#{getsection("S3").id},S3,#{stud5.user.id},,#{Enrollment.find_by_user_id_and_course_section_id(stud5.user.id, getsection("S3").id).id},active,85,B\n" +
"#{teacher.user.id},T1,#{sec4.id},S4,#{stud6.user.id},,#{Enrollment.find_by_user_id_and_course_section_id(stud6.user.id, sec4.id).id},active,90,A-\n"]
admin = user_model
server, server_thread, post_lines = start_test_http_server
@ps.posted_settings = @plugin.default_settings.merge({
:publish_endpoint => "http://localhost:#{server.addr[1]}/endpoint"
})
@ps.save!
@course.publish_final_grades(admin)
server_thread.join
post_lines.should == [
"POST /endpoint HTTP/1.1",
"Accept: */*",
"Content-Type: text/csv",
"",
"publisher_id,publisher_sis_id,section_id,section_sis_id,student_id," +
"student_sis_id,enrollment_id,enrollment_status,score,grade\n" +
"#{admin.id},,#{getsection("S1").id},S1,#{getpseudonym("S1").user.id},S1,#{getenroll("S1", "S1").id},active,70,C-\n" +
"#{admin.id},,#{getsection("S2").id},S2,#{getpseudonym("S2").user.id},S2,#{getenroll("S2", "S2").id},active,75,C\n" +
"#{admin.id},,#{getsection("S2").id},S2,#{getpseudonym("S3").user.id},S3,#{getenroll("S3", "S2").id},active,80,B-\n" +
"#{admin.id},,#{getsection("S1").id},S1,#{getpseudonym("S4").user.id},S4,#{getenroll("S4", "S1").id},active,0,F\n" +
"#{admin.id},,#{getsection("S3").id},S3,#{stud5.user.id},,#{Enrollment.find_by_user_id_and_course_section_id(stud5.user.id, getsection("S3").id).id},active,85,B\n" +
"#{admin.id},,#{sec4.id},S4,#{stud6.user.id},,#{Enrollment.find_by_user_id_and_course_section_id(stud6.user.id, sec4.id).id},active,90,A-\n"]
end
end
end
describe Course, 'tabs_available' do
def new_exernal_tool(context)
context.context_external_tools.new(:name => "bob", :consumer_key => "bob", :shared_secret => "bob", :domain => "example.com")
end
it "should not include external tools if not configured for course navigation" do
course_model
tool = new_exernal_tool @course
tool.settings[:user_navigation] = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
tool.has_course_navigation.should == false
@teacher = user_model
@course.enroll_teacher(@teacher).accept
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should_not be_include(tool.asset_string)
end
it "should include external tools if configured on the course" do
course_model
tool = new_exernal_tool @course
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
tab = tabs.detect{|t| t[:id] == tool.asset_string }
tab[:label].should == tool.settings[:course_navigation][:text]
tab[:href].should == :course_external_tool_path
tab[:args].should == [@course.id, tool.id]
end
it "should include external tools if configured on the account" do
course_model
@account = @course.root_account.sub_accounts.create!(:name => "sub-account")
@course.move_to_account(@account.root_account, @account)
tool = new_exernal_tool @account
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
tab = tabs.detect{|t| t[:id] == tool.asset_string }
tab[:label].should == tool.settings[:course_navigation][:text]
tab[:href].should == :course_external_tool_path
tab[:args].should == [@course.id, tool.id]
end
it "should include external tools if configured on the root account" do
course_model
@account = @course.root_account.sub_accounts.create!(:name => "sub-account")
@course.move_to_account(@account.root_account, @account)
tool = new_exernal_tool @account.root_account
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
tab = tabs.detect{|t| t[:id] == tool.asset_string }
tab[:label].should == tool.settings[:course_navigation][:text]
tab[:href].should == :course_external_tool_path
tab[:args].should == [@course.id, tool.id]
end
it "should only include admin-only external tools for course admins" do
course_model
@course.offer
@course.is_public = true
@course.save!
tool = new_exernal_tool @course
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL", :visibility => 'admins'}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
@student = user_model
@student.register!
@course.enroll_student(@student).accept
tabs = @course.tabs_available(nil)
tabs.map{|t| t[:id] }.should_not be_include(tool.asset_string)
tabs = @course.tabs_available(@student)
tabs.map{|t| t[:id] }.should_not be_include(tool.asset_string)
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
tab = tabs.detect{|t| t[:id] == tool.asset_string }
tab[:label].should == tool.settings[:course_navigation][:text]
tab[:href].should == :course_external_tool_path
tab[:args].should == [@course.id, tool.id]
end
it "should not include member-only external tools for unauthenticated users" do
course_model
@course.offer
@course.is_public = true
@course.save!
tool = new_exernal_tool @course
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL", :visibility => 'members'}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
@student = user_model
@student.register!
@course.enroll_student(@student).accept
tabs = @course.tabs_available(nil)
tabs.map{|t| t[:id] }.should_not be_include(tool.asset_string)
tabs = @course.tabs_available(@student)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
tab = tabs.detect{|t| t[:id] == tool.asset_string }
tab[:label].should == tool.settings[:course_navigation][:text]
tab[:href].should == :course_external_tool_path
tab[:args].should == [@course.id, tool.id]
end
it "should allow reordering external tool position in course navigation" do
course_model
tool = new_exernal_tool @course
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
@course.tab_configuration = Course.default_tabs.map{|t| {:id => t[:id] } }.insert(1, {:id => tool.asset_string})
@course.save!
tabs = @course.tabs_available(@teacher)
tabs[1][:id].should == tool.asset_string
end
it "should not show external tools that are hidden in course navigation" do
course_model
tool = new_exernal_tool @course
tool.settings[:course_navigation] = {:url => "http://www.example.com", :text => "Example URL"}
tool.save!
tool.has_course_navigation.should == true
@teacher = user_model
@course.enroll_teacher(@teacher).accept
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
@course.tab_configuration = Course.default_tabs.map{|t| {:id => t[:id] } }.insert(1, {:id => tool.asset_string, :hidden => true})
@course.save!
@course = Course.find(@course.id)
tabs = @course.tabs_available(@teacher)
tabs.map{|t| t[:id] }.should_not be_include(tool.asset_string)
tabs = @course.tabs_available(@teacher, :for_reordering => true)
tabs.map{|t| t[:id] }.should be_include(tool.asset_string)
end
end
describe Course, 'scoping' do
it 'should search by multiple fields' do
c1 = Course.new
c1.root_account = Account.create
c1.name = "name1"
c1.sis_source_id = "sisid1"
c1.course_code = "code1"
c1.save
c2 = Course.new
c2.root_account = Account.create
c2.name = "name2"
c2.course_code = "code2"
c2.sis_source_id = "sisid2"
c2.save
Course.name_like("name1").map(&:id).should == [c1.id]
Course.name_like("sisid2").map(&:id).should == [c2.id]
Course.name_like("code1").map(&:id).should == [c1.id]
end
end
describe Course, "manageable_by_user" do
it "should include courses associated with the user's active accounts" do
account = Account.create!
sub_account = Account.create!(:parent_account => account)
sub_sub_account = Account.create!(:parent_account => sub_account)
user = account_admin_user(:account => sub_account)
course = Course.create!(:account => sub_sub_account)
Course.manageable_by_user(user.id).map{ |c| c.id }.should be_include(course.id)
end
it "should include courses the user is actively enrolled in as a teacher" do
course = Course.create
user = user_with_pseudonym
course.enroll_teacher(user)
e = course.teacher_enrollments.first
e.accept
Course.manageable_by_user(user.id).map{ |c| c.id }.should be_include(course.id)
end
it "should include courses the user is actively enrolled in as a ta" do
course = Course.create
user = user_with_pseudonym
course.enroll_ta(user)
e = course.ta_enrollments.first
e.accept
Course.manageable_by_user(user.id).map{ |c| c.id }.should be_include(course.id)
end
it "should include courses the user is actively enrolled in as a designer" do
course = Course.create
user = user_with_pseudonym
course.enroll_designer(user).accept
Course.manageable_by_user(user.id).map{ |c| c.id }.should be_include(course.id)
end
it "should not include courses the user is enrolled in when the enrollment is non-active" do
course = Course.create
user = user_with_pseudonym
course.enroll_teacher(user)
e = course.teacher_enrollments.first
# it's only invited at this point
Course.manageable_by_user(user.id).should be_empty
e.destroy
Course.manageable_by_user(user.id).should be_empty
end
it "should not include deleted courses the user was enrolled in" do
course = Course.create
user = user_with_pseudonym
course.enroll_teacher(user)
e = course.teacher_enrollments.first
e.accept
course.destroy
Course.manageable_by_user(user.id).should be_empty
end
end
describe Course, "conclusions" do
it "should grant concluded users read but not participate" do
enrollment = course_with_student(:active_all => 1)
@course.reload
# active
@course.grants_rights?(@user, nil, :read, :participate_as_student).should == {:read => true, :participate_as_student => true}
# soft conclusion
enrollment.start_at = 4.days.ago
enrollment.end_at = 2.days.ago
enrollment.save!
@course.reload
@user.reload
@user.cached_current_enrollments(:reload)
enrollment.state.should == :active
enrollment.state_based_on_date.should == :completed
enrollment.should_not be_participating_student
@course.grants_rights?(@user, nil, :read, :participate_as_student).should == {:read => true, :participate_as_student => false}
# hard enrollment conclusion
enrollment.start_at = enrollment.end_at = nil
enrollment.workflow_state = 'completed'
enrollment.save!
@course.reload
@user.reload
@user.cached_current_enrollments(:reload)
enrollment.state.should == :completed
enrollment.state_based_on_date.should == :completed
@course.grants_rights?(@user, nil, :read, :participate_as_student).should == {:read => true, :participate_as_student => false}
# course conclusion
enrollment.workflow_state = 'active'
enrollment.save!
@course.reload
@course.complete!
@user.reload
@user.cached_current_enrollments(:reload)
enrollment.reload
enrollment.state.should == :completed
enrollment.state_based_on_date.should == :completed
@course.grants_rights?(@user, nil, :read, :participate_as_student).should == {:read => true, :participate_as_student => false}
end
context "appointment cancelation" do
before do
course_with_student(:active_all => true)
@ag = @course.appointment_groups.create(:title => "test", :new_appointments => [['2010-01-01 13:00:00', '2010-01-01 14:00:00'], ["#{Time.now.year + 1}-01-01 13:00:00", "#{Time.now.year + 1}-01-01 14:00:00"]])
@ag.appointments.each do |a|
a.reserve_for(@user, @user)
end
end
it "should cancel all future appointments when concluding an enrollment" do
@enrollment.conclude
@ag.appointments_participants.size.should eql 1
@ag.appointments_participants.current.size.should eql 0
end
it "should cancel all future appointments when concluding all enrollments" do
@course.complete!
@ag.appointments_participants.size.should eql 1
@ag.appointments_participants.current.size.should eql 0
end
end
end
describe Course, "inherited_assessment_question_banks" do
it "should include the course's banks if include_self is true" do
@account = Account.create
@course = Course.create(:account => @account)
@course.inherited_assessment_question_banks(true).should be_empty
bank = @course.assessment_question_banks.create
@course.inherited_assessment_question_banks(true).should eql [bank]
end
it "should include all banks in the account hierarchy" do
@root_account = Account.create
root_bank = @root_account.assessment_question_banks.create
@account = Account.new
@account.root_account = @root_account
@account.save
account_bank = @account.assessment_question_banks.create
@course = Course.create(:account => @account)
@course.inherited_assessment_question_banks.sort_by(&:id).should eql [root_bank, account_bank]
end
it "should return a useful scope" do
@root_account = Account.create
root_bank = @root_account.assessment_question_banks.create
@account = Account.new
@account.root_account = @root_account
@account.save
account_bank = @account.assessment_question_banks.create
@course = Course.create(:account => @account)
bank = @course.assessment_question_banks.create
banks = @course.inherited_assessment_question_banks(true)
banks.scoped(:order => :id).should eql [root_bank, account_bank, bank]
banks.find_by_id(bank.id).should eql bank
banks.find_by_id(account_bank.id).should eql account_bank
banks.find_by_id(root_bank.id).should eql root_bank
end
end
describe Course, "section_visibility" do
before do
@course = course(:active_course => true)
@course.default_section
@other_section = @course.course_sections.create
@teacher = User.create
@course.enroll_teacher(@teacher)
@ta = User.create
@course.enroll_user(@ta, "TaEnrollment", :limit_privileges_to_course_section => true)
@student1 = User.create
@course.enroll_user(@student1, "StudentEnrollment", :enrollment_state => 'active')
@student2 = User.create
@course.enroll_user(@student2, "StudentEnrollment", :section => @other_section, :enrollment_state => 'active')
@observer = User.create
@course.enroll_user(@observer, "ObserverEnrollment")
end
context "full" do
it "should return students from all sections" do
@course.students_visible_to(@teacher).sort_by(&:id).should eql [@student1, @student2]
@course.students_visible_to(@student1).sort_by(&:id).should eql [@student1, @student2]
end
it "should return all sections if a teacher" do
@course.sections_visible_to(@teacher).sort_by(&:id).should eql [@course.default_section, @other_section]
end
it "should return user's sections if a student" do
@course.sections_visible_to(@student1).should eql [@course.default_section]
end
end
context "sections" do
it "should return students from user's sections" do
@course.students_visible_to(@ta).should eql [@student1]
end
it "should return user's sections" do
@course.sections_visible_to(@ta).should eql [@course.default_section]
end
end
context "restricted" do
it "should return no students" do
@course.students_visible_to(@observer).should eql []
end
it "should return no sections" do
@course.sections_visible_to(@observer).should eql []
end
end
context "migrate_content_links" do
it "should ignore types not in the supported_types arg" do
c1 = course_model
c2 = course_model
orig = <<-HTML
We aren't translating <a href="/courses/#{c1.id}/assignments/5">links to assignments</a>
HTML
html = Course.migrate_content_links(orig, c1, c2, ['files'])
html.should == orig
end
end
it "should be marshal-able" do
c = Course.new(:name => 'c1')
Marshal.dump(c)
c.save!
Marshal.dump(c)
end
end
describe Course, ".import_from_migration" do
before do
attachment_model(:uploaded_data => stub_file_data('test.m4v', 'asdf', 'video/mp4'))
course_with_teacher
end
it "should wait for media objects on canvas cartridge import" do
migration = mock(:migration_settings => { 'worker_class' => 'CC::Importer::Canvas::Converter' }.with_indifferent_access)
MediaObject.expects(:add_media_files).with([@attachment], true)
@course.import_media_objects([@attachment], migration)
end
it "should not wait for media objects on other import" do
migration = mock(:migration_settings => { 'worker_class' => 'CC::Importer::Standard::Converter' }.with_indifferent_access)
MediaObject.expects(:add_media_files).with([@attachment], false)
@course.import_media_objects([@attachment], migration)
end
it "should know when it has open course imports" do
# no course imports
@course.should_not have_open_course_imports
# created course import
@course.course_imports.create!
@course.should have_open_course_imports
# started course import
@course.course_imports.first.update_attribute(:workflow_state, 'started')
@course.should have_open_course_imports
# completed course import
@course.course_imports.first.update_attribute(:workflow_state, 'completed')
@course.should_not have_open_course_imports
# failed course import
@course.course_imports.first.update_attribute(:workflow_state, 'failed')
@course.should_not have_open_course_imports
end
end
describe Course, "enrollments" do
it "should update enrollments' root_account_id when necessary" do
a1 = Account.create!
a2 = Account.create!
course_with_student
@course.root_account = a1
@course.save!
@course.student_enrollments.map(&:root_account_id).should eql [a1.id]
@course.root_account = a2
@course.save!
@course.student_enrollments(true).map(&:root_account_id).should eql [a2.id]
end
end
describe Course, "user_is_teacher?" do
it "should be true for teachers" do
course = Course.create
teacher = user_with_pseudonym
course.enroll_teacher(teacher).accept
course.user_is_teacher?(teacher).should be_true
end
it "should be false for designers" do
course = Course.create
ta = user_with_pseudonym
course.enroll_ta(ta).accept
course.user_is_teacher?(ta).should be_true
end
it "should be false for designers" do
course = Course.create
designer = user_with_pseudonym
course.enroll_designer(designer).accept
course.user_is_teacher?(designer).should be_false
end
end
describe Course, "user_has_been_teacher?" do
it "should be true for teachers, past or present" do
e = course_with_teacher(:active_all => true)
@course.user_has_been_teacher?(@teacher).should be_true
e.conclude
e.reload.workflow_state.should == "completed"
@course.user_has_been_teacher?(@teacher).should be_true
@course.complete
@course.user_has_been_teacher?(@teacher).should be_true
end
end
describe Course, "user_has_been_student?" do
it "should be true for students, past or present" do
e = course_with_student(:active_all => true)
@course.user_has_been_student?(@student).should be_true
e.conclude
e.reload.workflow_state.should == "completed"
@course.user_has_been_student?(@student).should be_true
@course.complete
@course.user_has_been_student?(@student).should be_true
end
end
describe Course, "student_view_student" do
before(:each) do
course_with_teacher(:active_all => true)
end
it "should create and return the student view student for a course" do
expect { @course.student_view_student }.to change(User, :count).by(1)
end
it "should find and return the student view student on successive calls" do
@course.student_view_student
expect { @course.student_view_student }.to change(User, :count).by(0)
end
it "should create enrollments for each section" do
@section2 = @course.course_sections.create!
expect { @fake_student = @course.student_view_student }.to change(Enrollment, :count).by(2)
@fake_student.enrollments.all?{|e| e.fake_student?}.should be_true
end
it "should sync enrollments after being created" do
@course.student_view_student
@section2 = @course.course_sections.create!
expect { @course.student_view_student }.to change(Enrollment, :count).by(1)
end
it "should create a pseudonym for the fake student" do
expect { @fake_student = @course.student_view_student }.to change(Pseudonym, :count).by(1)
@fake_student.pseudonyms.should_not be_empty
end
it "should allow two different student view users for two different courses" do
@course1 = @course
@teacher1 = @teacher
course_with_teacher(:active_all => true)
@course2 = @course
@teacher2 = @teacher
@fake_student1 = @course1.student_view_student
@fake_student2 = @course2.student_view_student
@fake_student1.id.should_not eql @fake_student2.id
@fake_student1.pseudonym.id.should_not eql @fake_student2.pseudonym.id
end
end
describe Course do
describe "user_list_search_mode_for" do
it "should be open for anyone if open registration is turned on" do
account = Account.default
account.settings = { :open_registration => true }
account.save!
course
@course.user_list_search_mode_for(nil).should == :open
@course.user_list_search_mode_for(user).should == :open
end
it "should be preferred for account admins" do
account = Account.default
course
@course.user_list_search_mode_for(nil).should == :closed
@course.user_list_search_mode_for(user).should == :closed
user
account.add_user(@user)
@course.user_list_search_mode_for(@user).should == :preferred
end
it "should be preferred if delegated authentication is configured" do
account = Account.default
account.settings = { :open_registration => true }
account.account_authorization_configs.create!(:auth_type => 'cas')
account.save!
course
@course.user_list_search_mode_for(nil).should == :preferred
@course.user_list_search_mode_for(user).should == :preferred
end
end
end