3267 lines
122 KiB
Ruby
3267 lines
122 KiB
Ruby
#
|
|
# Copyright (C) 2011 Instructure, Inc.
|
|
#
|
|
# This file is part of Canvas.
|
|
#
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, version 3 of the License.
|
|
#
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
|
|
|
describe Assignment do
|
|
before :once do
|
|
course_with_teacher(active_all: true)
|
|
student_in_course(active_all: true, user_name: "some user")
|
|
end
|
|
|
|
it "should create a new instance given valid attributes" do
|
|
@course.assignments.create!(assignment_valid_attributes)
|
|
end
|
|
|
|
it "should have a useful state machine" do
|
|
assignment_model(course: @course)
|
|
expect(@a.state).to eql(:published)
|
|
@a.unpublish
|
|
expect(@a.state).to eql(:unpublished)
|
|
end
|
|
|
|
it "should always be associated with a group" do
|
|
assignment_model(course: @course)
|
|
@assignment.save!
|
|
expect(@assignment.assignment_group).not_to be_nil
|
|
end
|
|
|
|
it "should be associated with a group when the course has no active groups" do
|
|
@course.require_assignment_group
|
|
@course.assignment_groups.first.destroy
|
|
expect(@course.assignment_groups.size).to eq 1
|
|
expect(@course.assignment_groups.active.size).to eq 0
|
|
@assignment = assignment_model(:course => @course)
|
|
expect(@assignment.assignment_group).not_to be_nil
|
|
end
|
|
|
|
it "should touch assignment group on create/save" do
|
|
group = @course.assignment_groups.create!(:name => "Assignments")
|
|
AssignmentGroup.where(:id => group).update_all(:updated_at => 1.hour.ago)
|
|
orig_time = group.reload.updated_at.to_i
|
|
a = @course.assignments.build("title"=>"test")
|
|
a.assignment_group = group
|
|
a.save!
|
|
expect(@course.assignments.count).to eq 1
|
|
group.reload
|
|
expect(group.updated_at.to_i).not_to eq orig_time
|
|
end
|
|
|
|
it "should be able to submit homework" do
|
|
setup_assignment_with_homework
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
expect(@submission.versions.length).to eql(1)
|
|
end
|
|
|
|
it "should validate grading_type inclusion" do
|
|
@invalid_grading_type = "invalid"
|
|
@assignment = Assignment.new(assignment_valid_attributes.merge({
|
|
course: @course,
|
|
grading_type: @invalid_grading_type
|
|
}))
|
|
|
|
expect(@assignment).not_to be_valid
|
|
expect(@assignment.errors[:grading_type]).not_to be_nil
|
|
end
|
|
|
|
describe "#has_student_submissions?" do
|
|
before :once do
|
|
setup_assignment_with_students
|
|
end
|
|
|
|
it "does not allow itself to be unpublished if it has student submissions" do
|
|
@assignment.submit_homework @stu1, :submission_type => "online_text_entry"
|
|
expect(@assignment).not_to be_can_unpublish
|
|
|
|
@assignment.unpublish
|
|
expect(@assignment).not_to be_valid
|
|
expect(@assignment.errors['workflow_state']).to eq ["Can't unpublish if there are student submissions"]
|
|
end
|
|
|
|
it "does allow itself to be unpublished if it has nil submissions" do
|
|
@assignment.submit_homework @stu1, :submission_type => nil
|
|
expect(@assignment).to be_can_unpublish
|
|
@assignment.unpublish
|
|
expect(@assignment.workflow_state).to eq "unpublished"
|
|
end
|
|
end
|
|
|
|
describe '#grade_student' do
|
|
before(:once) { setup_assignment_without_submission }
|
|
|
|
describe 'with a valid student' do
|
|
before :once do
|
|
@result = @assignment.grade_student(@user, :grade => "10")
|
|
@assignment.reload
|
|
end
|
|
|
|
it 'returns an array' do
|
|
expect(@result).to be_is_a(Array)
|
|
end
|
|
|
|
it 'now has a submission' do
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
end
|
|
|
|
describe 'the submission after grading' do
|
|
subject { @assignment.submissions.first }
|
|
|
|
describe '#state' do
|
|
subject { super().state }
|
|
it { is_expected.to eql(:graded) }
|
|
end
|
|
it { is_expected.to eq @result[0] }
|
|
|
|
describe '#score' do
|
|
subject { super().score }
|
|
it { is_expected.to eq 10.0 }
|
|
end
|
|
|
|
describe '#user_id' do
|
|
subject { super().user_id }
|
|
it { is_expected.to eq @user.id }
|
|
end
|
|
specify { expect(subject.versions.length).to eq 1 }
|
|
end
|
|
end
|
|
|
|
it 'raises an error if there is no student' do
|
|
expect { @assignment.grade_student(nil) }.to raise_error(StandardError, 'Student is required')
|
|
end
|
|
|
|
it 'will not continue if the student does not belong here' do
|
|
expect { @assignment.grade_student(User.new) }.to raise_error(StandardError, 'Student must be enrolled in the course as a student to be graded')
|
|
end
|
|
end
|
|
|
|
it "should update a submission's graded_at when grading it" do
|
|
setup_assignment_with_homework
|
|
@assignment.grade_student(@user, :grade => 1)
|
|
@submission = @assignment.submissions.first
|
|
original_graded_at = @submission.graded_at
|
|
new_time = Time.now + 1.hour
|
|
Time.stubs(:now).returns(new_time)
|
|
@assignment.grade_student(@user, :grade => 2)
|
|
@submission.reload
|
|
expect(@submission.graded_at).not_to eql original_graded_at
|
|
end
|
|
|
|
context "needs_grading_count" do
|
|
before :once do
|
|
setup_assignment_with_homework
|
|
end
|
|
|
|
it "should update when submissions transition state" do
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
@assignment.grade_student(@user, :grade => "0")
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
end
|
|
|
|
it "should not update when non-student submissions transition state" do
|
|
assignment_model(course: @course)
|
|
s = @assignment.find_or_create_submission(@teacher)
|
|
s.submission_type = 'online_quiz'
|
|
s.workflow_state = 'submitted'
|
|
s.save!
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
s.workflow_state = 'graded'
|
|
s.save!
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
end
|
|
|
|
it "should update when enrollment changes" do
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
@course.enrollments.where(user_id: @user.id).first.destroy
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
e = @course.enroll_student(@user)
|
|
e.invite
|
|
e.accept
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
|
|
# multiple enrollments should not cause double-counting (either by creating as or updating into "active")
|
|
section2 = @course.course_sections.create!(:name => 's2')
|
|
e2 = @course.enroll_student(@user,
|
|
:enrollment_state => 'invited',
|
|
:section => section2,
|
|
:allow_multiple_enrollments => true)
|
|
e2.accept
|
|
section3 = @course.course_sections.create!(:name => 's2')
|
|
e3 = @course.enroll_student(@user,
|
|
:enrollment_state => 'active',
|
|
:section => section3,
|
|
:allow_multiple_enrollments => true)
|
|
expect(@user.enrollments.where(:workflow_state => 'active').count).to eql(3)
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
|
|
# and as long as one enrollment is still active, the count should not change
|
|
e2.destroy
|
|
e3.complete
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
|
|
# ok, now gone for good
|
|
e.destroy
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
expect(@user.enrollments.where(:workflow_state => 'active').count).to eql(0)
|
|
|
|
# enroll the user as a teacher, it should have no effect
|
|
e4 = @course.enroll_teacher(@user)
|
|
e4.accept
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
expect(@user.enrollments.where(:workflow_state => 'active').count).to eql(1)
|
|
end
|
|
|
|
it "updated_at should be set when needs_grading_count changes due to a submission" do
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
old_timestamp = Time.now.utc - 1.minute
|
|
Assignment.where(:id => @assignment).update_all(:updated_at => old_timestamp)
|
|
@assignment.grade_student(@user, :grade => "0")
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
expect(@assignment.updated_at).to be > old_timestamp
|
|
end
|
|
|
|
it "updated_at should be set when needs_grading_count changes due to an enrollment change" do
|
|
old_timestamp = Time.now.utc - 1.minute
|
|
expect(@assignment.needs_grading_count).to eql(1)
|
|
Assignment.where(:id => @assignment).update_all(:updated_at => old_timestamp)
|
|
@course.enrollments.where(user_id: @user).first.destroy
|
|
@assignment.reload
|
|
expect(@assignment.needs_grading_count).to eql(0)
|
|
expect(@assignment.updated_at).to be > old_timestamp
|
|
end
|
|
end
|
|
|
|
context "differentiated_assignment visibility" do
|
|
describe "students_with_visibility" do
|
|
before :once do
|
|
setup_differentiated_assignments
|
|
end
|
|
|
|
context "differentiated_assignment on" do
|
|
it "should return assignments only when a student has overrides" do
|
|
expect(@assignment.students_with_visibility.include?(@student1)).to be_truthy
|
|
expect(@assignment.students_with_visibility.include?(@student2)).to be_falsey
|
|
end
|
|
|
|
it "should not return students outside the class" do
|
|
expect(@assignment.students_with_visibility.include?(@student3)).to be_falsey
|
|
end
|
|
end
|
|
|
|
context "differentiated_assignment off" do
|
|
before {@course.disable_feature!(:differentiated_assignments)}
|
|
it "should return all published assignments" do
|
|
expect(@assignment.students_with_visibility.include?(@student1)).to be_truthy
|
|
expect(@assignment.students_with_visibility.include?(@student2)).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "permissions" do
|
|
before :once do
|
|
@course.enable_feature!(:differentiated_assignments)
|
|
@assignment.submission_types = "online_text_entry"
|
|
@assignment.save!
|
|
end
|
|
|
|
it "should not allow students without visibility to submit" do
|
|
expect(@assignment.check_policy(@student1)).to include :submit
|
|
expect(@assignment.check_policy(@student2)).not_to include :submit
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "grading" do
|
|
before :once do
|
|
setup_assignment_without_submission
|
|
end
|
|
|
|
it "should preserve pass/fail with zero points possible" do
|
|
@assignment.grading_type = 'pass_fail'
|
|
@assignment.points_possible = 0.0
|
|
@assignment.save
|
|
s = @assignment.grade_student(@user, :grade => 'pass')
|
|
expect(s).to be_is_a(Array)
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission).to eql(s[0])
|
|
expect(@submission.score).to eql(0.0)
|
|
expect(@submission.grade).to eql('complete')
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
|
|
@assignment.grade_student(@user, :grade => 'fail')
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission).to eql(s[0])
|
|
expect(@submission.score).to eql(0.0)
|
|
expect(@submission.grade).to eql('incomplete')
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
end
|
|
|
|
it "should preserve letter grades with zero points possible" do
|
|
@assignment.grading_type = 'letter_grade'
|
|
@assignment.points_possible = 0.0
|
|
@assignment.save!
|
|
|
|
s = @assignment.grade_student(@user, :grade => 'C')
|
|
expect(s).to be_is_a(Array)
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission.score).to eql(0.0)
|
|
expect(@submission.grade).to eql('C')
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
end
|
|
|
|
it "should properly calculate letter grades" do
|
|
@assignment.grading_type = 'letter_grade'
|
|
@assignment.points_possible = 10
|
|
grade = @assignment.score_to_grade(8.7)
|
|
expect(grade).to eql("B+")
|
|
end
|
|
|
|
it "should properly allow decimal points in grading" do
|
|
@assignment.grading_type = 'letter_grade'
|
|
@assignment.points_possible = 10
|
|
grade = @assignment.score_to_grade(8.6999)
|
|
expect(grade).to eql("B")
|
|
end
|
|
|
|
it "should properly compute pass/fail for nil" do
|
|
@assignment.grading_type = 'pass_fail'
|
|
@assignment.points_possible = 10
|
|
grade = @assignment.score_to_grade(nil)
|
|
expect(grade).to eql("incomplete")
|
|
end
|
|
|
|
it "should preserve letter grades grades with nil points possible" do
|
|
@assignment.grading_type = 'letter_grade'
|
|
@assignment.points_possible = nil
|
|
@assignment.save!
|
|
|
|
s = @assignment.grade_student(@user, :grade => 'C')
|
|
expect(s).to be_is_a(Array)
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission.score).to eql(0.0)
|
|
expect(@submission.grade).to eql('C')
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
end
|
|
|
|
it "should preserve gpa scale grades with nil points possible" do
|
|
@assignment.grading_type = 'gpa_scale'
|
|
@assignment.points_possible = nil
|
|
@assignment.context.grading_standards.build({title: "GPA"})
|
|
gs = @assignment.context.grading_standards.last
|
|
gs.data = {"4.0" => 0.94,
|
|
"3.7" => 0.90,
|
|
"3.3" => 0.87,
|
|
"3.0" => 0.84,
|
|
"2.7" => 0.80,
|
|
"2.3" => 0.77,
|
|
"2.0" => 0.74,
|
|
"1.7" => 0.70,
|
|
"1.3" => 0.67,
|
|
"1.0" => 0.64,
|
|
"0" => 0.01,
|
|
"M" => 0.0 }
|
|
gs.assignments << @assignment
|
|
gs.save!
|
|
@assignment.save!
|
|
|
|
s = @assignment.grade_student(@user, :grade => '3.0')
|
|
expect(s).to be_is_a(Array)
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission.score).to eql(0.0)
|
|
expect(@submission.grade).to eql('3.0')
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
end
|
|
|
|
it "should preserve gpa scale grades with zero points possible" do
|
|
@assignment.grading_type = 'gpa_scale'
|
|
@assignment.points_possible = 0.0
|
|
@assignment.context.grading_standards.build({title: "GPA"})
|
|
gs = @assignment.context.grading_standards.last
|
|
gs.data = {"4.0" => 0.94,
|
|
"3.7" => 0.90,
|
|
"3.3" => 0.87,
|
|
"3.0" => 0.84,
|
|
"2.7" => 0.80,
|
|
"2.3" => 0.77,
|
|
"2.0" => 0.74,
|
|
"1.7" => 0.70,
|
|
"1.3" => 0.67,
|
|
"1.0" => 0.64,
|
|
"0" => 0.01,
|
|
"M" => 0.0 }
|
|
gs.assignments << @assignment
|
|
gs.save!
|
|
@assignment.save!
|
|
|
|
s = @assignment.grade_student(@user, :grade => '3.0')
|
|
expect(s).to be_is_a(Array)
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission.score).to eql(0.0)
|
|
expect(@submission.grade).to eql('3.0')
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
end
|
|
|
|
it "should handle percent grades with nil points possible" do
|
|
@assignment.grading_type = "percent"
|
|
@assignment.points_possible = nil
|
|
grade = @assignment.score_to_grade(5.0)
|
|
expect(grade).to eql('5%')
|
|
end
|
|
|
|
it "should give a grade to extra credit assignments" do
|
|
@assignment.grading_type = 'points'
|
|
@assignment.points_possible = 0.0
|
|
@assignment.save
|
|
s = @assignment.grade_student(@user, :grade => "1")
|
|
expect(s).to be_is_a(Array)
|
|
@assignment.reload
|
|
expect(@assignment.submissions.size).to eql(1)
|
|
@submission = @assignment.submissions.first
|
|
expect(@submission.state).to eql(:graded)
|
|
expect(@submission).to eql(s[0])
|
|
expect(@submission.score).to eql(1.0)
|
|
expect(@submission.grade).to eql("1")
|
|
expect(@submission.user_id).to eql(@user.id)
|
|
|
|
@submission.score = 2.0
|
|
@submission.save
|
|
@submission.reload
|
|
expect(@submission.grade).to eql("2")
|
|
end
|
|
|
|
it "should be able to grade an already-existing submission" do
|
|
s = @a.submit_homework(@user)
|
|
s2 = @a.grade_student(@user, :grade => "10")
|
|
s.reload
|
|
expect(s).to eql(s2[0])
|
|
# there should only be one version, even though the grade changed
|
|
expect(s.versions.length).to eql(1)
|
|
expect(s2[0].state).to eql(:graded)
|
|
end
|
|
end
|
|
|
|
describe "interpret_grade" do
|
|
before :once do
|
|
setup_assignment_without_submission
|
|
end
|
|
|
|
it "should return nil when no grade was entered and assignment uses a grading standard (letter grade)" do
|
|
@assignment.points_possible = 100
|
|
expect(@assignment.interpret_grade("")).to be_nil
|
|
end
|
|
|
|
it "should allow grading an assignment with nil points_possible" do
|
|
@assignment.points_possible = nil
|
|
expect(@assignment.interpret_grade("100%")).to eq 0
|
|
end
|
|
|
|
it "should not round scores" do
|
|
@assignment.points_possible = 15
|
|
expect(@assignment.interpret_grade("88.75%")).to eq 13.3125
|
|
end
|
|
end
|
|
|
|
describe '#submit_homework' do
|
|
before(:once) do
|
|
course_with_student(active_all: true)
|
|
@a = @course.assignments.create! title: "blah",
|
|
submission_types: "online_text_entry,online_url",
|
|
points_possible: 10
|
|
end
|
|
|
|
it "creates a new version for each submission" do
|
|
setup_assignment_without_submission
|
|
@a.submit_homework(@user)
|
|
@a.submit_homework(@user)
|
|
@a.submit_homework(@user)
|
|
@a.reload
|
|
expect(@a.submissions.first.versions.length).to eql(3)
|
|
end
|
|
|
|
it "doesn't mark as submitted if no submission" do
|
|
s = @a.submit_homework(@user)
|
|
expect(s.workflow_state).to eq "unsubmitted"
|
|
end
|
|
|
|
it "clears out stale submission information" do
|
|
s = @a.submit_homework(@user, submission_type: "online_url",
|
|
url: "http://example.com")
|
|
expect(s.submission_type).to eq "online_url"
|
|
expect(s.url).to eq "http://example.com"
|
|
|
|
s2 = @a.submit_homework(@user, submission_type: "online_text_entry",
|
|
body: "blah blah blah blah blah blah blah")
|
|
expect(s2.submission_type).to eq "online_text_entry"
|
|
expect(s2.body).to eq "blah blah blah blah blah blah blah"
|
|
expect(s2.url).to be_nil
|
|
expect(s2.workflow_state).to eq "submitted"
|
|
|
|
# comments shouldn't clear out submission data
|
|
s3 = @a.submit_homework(@user, comment: "BLAH BLAH")
|
|
expect(s3.body).to eq "blah blah blah blah blah blah blah"
|
|
expect(s3.submission_comments.first.comment).to eq "BLAH BLAH"
|
|
expect(s3.submission_type).to eq "online_text_entry"
|
|
end
|
|
end
|
|
|
|
describe "muting" do
|
|
before :once do
|
|
assignment_model(course: @course)
|
|
end
|
|
|
|
it "should default to unmuted" do
|
|
expect(@assignment.muted?).to eql false
|
|
end
|
|
|
|
it "should be mutable" do
|
|
expect(@assignment.respond_to?(:mute!)).to eql true
|
|
@assignment.mute!
|
|
expect(@assignment.muted?).to eql true
|
|
end
|
|
|
|
it "should be unmutable" do
|
|
expect(@assignment.respond_to?(:unmute!)).to eql true
|
|
@assignment.mute!
|
|
@assignment.unmute!
|
|
expect(@assignment.muted?).to eql false
|
|
end
|
|
end
|
|
|
|
describe "infer_times" do
|
|
it "should set to all_day" do
|
|
assignment_model(:due_at => "Sep 3 2008 12:00am",
|
|
:lock_at => "Sep 3 2008 12:00am",
|
|
:unlock_at => "Sep 3 2008 12:00am",
|
|
:course => @course)
|
|
expect(@assignment.all_day).to eql(false)
|
|
@assignment.infer_times
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eql(true)
|
|
expect(@assignment.due_at.strftime("%H:%M")).to eql("23:59")
|
|
expect(@assignment.lock_at.strftime("%H:%M")).to eql("23:59")
|
|
expect(@assignment.unlock_at.strftime("%H:%M")).to eql("00:00")
|
|
expect(@assignment.all_day_date).to eql(Date.parse("Sep 3 2008"))
|
|
end
|
|
|
|
it "should not set to all_day without infer_times call" do
|
|
assignment_model(:due_at => "Sep 3 2008 12:00am",
|
|
:course => @course)
|
|
expect(@assignment.all_day).to eql(false)
|
|
expect(@assignment.due_at.strftime("%H:%M")).to eql("00:00")
|
|
expect(@assignment.all_day_date).to eql(Date.parse("Sep 3 2008"))
|
|
end
|
|
end
|
|
|
|
describe "all_day and all_day_date from due_at" do
|
|
def fancy_midnight(opts={})
|
|
zone = opts[:zone] || Time.zone
|
|
Time.use_zone(zone) do
|
|
time = opts[:time] || Time.zone.now
|
|
time.in_time_zone.midnight + 1.day - 1.minute
|
|
end
|
|
end
|
|
|
|
before :once do
|
|
@assignment = assignment_model(course: @course)
|
|
end
|
|
|
|
it "should interpret 11:59pm as all day with no prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq true
|
|
end
|
|
|
|
it "should interpret 11:59pm as all day with same-tz all-day prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.day
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq true
|
|
end
|
|
|
|
it "should interpret 11:59pm as all day with other-tz all-day prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Baghdad')
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq true
|
|
end
|
|
|
|
it "should interpret 11:59pm as all day with non-all-day prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq true
|
|
end
|
|
|
|
it "should not interpret non-11:59pm as all day no prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
|
@assignment.time_zone_edited = 'Baghdad'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq false
|
|
end
|
|
|
|
it "should not interpret non-11:59pm as all day with same-tz all-day prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq false
|
|
end
|
|
|
|
it "should not interpret non-11:59pm as all day with other-tz all-day prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Baghdad')
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq false
|
|
end
|
|
|
|
it "should not interpret non-11:59pm as all day with non-all-day prior value" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 2.hour
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq false
|
|
end
|
|
|
|
it "should preserve all-day when only changing time zone" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
|
@assignment.time_zone_edited = 'Baghdad'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq true
|
|
end
|
|
|
|
it "should preserve non-all-day when only changing time zone" do
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
|
@assignment.save!
|
|
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day).to eq false
|
|
end
|
|
|
|
it "should determine date from due_at's timezone" do
|
|
@assignment.due_at = Date.today.in_time_zone('Baghdad') + 1.hour # 01:00:00 AST +03:00 today
|
|
@assignment.time_zone_edited = 'Baghdad'
|
|
@assignment.save!
|
|
expect(@assignment.all_day_date).to eq Date.today
|
|
|
|
@assignment.due_at = @assignment.due_at.in_time_zone('Alaska') - 2.hours # 12:00:00 AKDT -08:00 previous day
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day_date).to eq Date.today - 1.day
|
|
end
|
|
|
|
it "should preserve all-day date when only changing time zone" do
|
|
@assignment.due_at = Date.today.in_time_zone('Baghdad') # 00:00:00 AST +03:00 today
|
|
@assignment.time_zone_edited = 'Baghdad'
|
|
@assignment.save!
|
|
@assignment.due_at = @assignment.due_at.in_time_zone('Alaska') # 13:00:00 AKDT -08:00 previous day
|
|
@assignment.time_zone_edited = 'Alaska'
|
|
@assignment.save!
|
|
expect(@assignment.all_day_date).to eq Date.today
|
|
end
|
|
|
|
it "should preserve non-all-day date when only changing time zone" do
|
|
@assignment.due_at = Date.today.in_time_zone('Alaska') - 11.hours # 13:00:00 AKDT -08:00 previous day
|
|
@assignment.save!
|
|
@assignment.due_at = @assignment.due_at.in_time_zone('Baghdad') # 00:00:00 AST +03:00 today
|
|
@assignment.time_zone_edited = 'Baghdad'
|
|
@assignment.save!
|
|
expect(@assignment.all_day_date).to eq Date.today - 1.day
|
|
end
|
|
end
|
|
|
|
it "should destroy group overrides when the group category changes" do
|
|
@assignment = assignment_model(course: @course)
|
|
@assignment.group_category = group_category(context: @assignment.context)
|
|
@assignment.save!
|
|
|
|
overrides = 5.times.map do
|
|
override = @assignment.assignment_overrides.build
|
|
override.set = @assignment.group_category.groups.create!(context: @assignment.context)
|
|
override.save!
|
|
|
|
expect(override.workflow_state).to eq 'active'
|
|
override
|
|
end
|
|
|
|
@assignment.group_category = group_category(context: @assignment.context, name: "bar")
|
|
@assignment.save!
|
|
|
|
overrides.each do |override|
|
|
override.reload
|
|
|
|
expect(override.workflow_state).to eq 'deleted'
|
|
expect(override.versions.size).to eq 2
|
|
expect(override.assignment_version).to eq @assignment.version_number
|
|
end
|
|
end
|
|
|
|
context "concurrent inserts" do
|
|
before :once do
|
|
assignment_model(course: @course)
|
|
@assignment.context.reload
|
|
|
|
@assignment.submissions.scoped.delete_all
|
|
end
|
|
|
|
def concurrent_inserts
|
|
real_sub = @assignment.submissions.build(user: @user)
|
|
|
|
mock_submissions = Submission.none
|
|
mock_submissions.stubs(:build).returns(real_sub).once
|
|
@assignment.stubs(:submissions).returns(mock_submissions)
|
|
|
|
sub = nil
|
|
expect {
|
|
sub = yield(@assignment, @user)
|
|
}.not_to raise_error
|
|
|
|
expect(sub).not_to be_new_record
|
|
expect(sub).to eql real_sub
|
|
end
|
|
|
|
it "should handle them gracefully in find_or_create_submission" do
|
|
concurrent_inserts do |assignment, user|
|
|
assignment.find_or_create_submission(user)
|
|
end
|
|
end
|
|
|
|
it "should handle them gracefully in submit_homework" do
|
|
concurrent_inserts do |assignment, user|
|
|
assignment.submit_homework(user, :body => "test")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "peer reviews" do
|
|
before :once do
|
|
assignment_model(course: @course)
|
|
end
|
|
|
|
context "basic assignment" do
|
|
before :once do
|
|
@users = create_users_in_course(@course, 10.times.map{ |i| {name: "user #{i}"} }, return_type: :record)
|
|
@a.reload
|
|
@submissions = @users.map do |u|
|
|
@a.submit_homework(u, :submission_type => "online_url", :url => "http://www.google.com")
|
|
end
|
|
end
|
|
|
|
it "should assign peer reviews" do
|
|
@a.peer_review_count = 1
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length)
|
|
@submissions.each do |s|
|
|
expect(res.map{|a| a.asset}).to be_include(s)
|
|
expect(res.map{|a| a.assessor_asset}).to be_include(s)
|
|
end
|
|
end
|
|
|
|
it "should assign when already graded" do
|
|
@users.each do |u|
|
|
@a.grade_student(u, :grader => @teacher, :grade => '100')
|
|
end
|
|
@a.peer_review_count = 1
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length)
|
|
@submissions.each do |s|
|
|
expect(res.map{|a| a.asset}).to be_include(s)
|
|
expect(res.map{|a| a.assessor_asset}).to be_include(s)
|
|
end
|
|
end
|
|
end
|
|
|
|
it "should schedule auto_assign when variables are right" do
|
|
@a.peer_reviews = true
|
|
@a.automatic_peer_reviews = true
|
|
@a.due_at = Time.zone.now
|
|
|
|
expects_job_with_tag('Assignment#do_auto_peer_review') {
|
|
@a.save!
|
|
}
|
|
end
|
|
|
|
it "should reset peer_reviews_assigned when the assign_at time changes" do
|
|
@a.peer_reviews = true
|
|
@a.automatic_peer_reviews = true
|
|
@a.due_at = 1.day.ago
|
|
@a.peer_reviews_assigned = true
|
|
@a.save!
|
|
|
|
@a.assign_peer_reviews
|
|
expect(@a.peer_reviews_assigned).to be_truthy
|
|
|
|
@a.peer_reviews_assign_at = 1.day.from_now
|
|
@a.save!
|
|
|
|
expect(@a.peer_reviews_assigned).to be_falsey
|
|
end
|
|
|
|
it "should allow setting peer_reviews_assign_at" do
|
|
now = Time.now
|
|
@assignment.peer_reviews_assign_at = now
|
|
expect(@assignment.peer_reviews_assign_at).to eq now
|
|
end
|
|
|
|
it "should assign multiple peer reviews" do
|
|
@a.reload
|
|
@submissions = []
|
|
users = create_users_in_course(@course, 3.times.map{ |i| {name: "user #{i}"} }, return_type: :record)
|
|
users.each do |u|
|
|
@submissions << @a.submit_homework(u, :submission_type => "online_url", :url => "http://www.google.com")
|
|
end
|
|
@a.peer_review_count = 2
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length * 2)
|
|
@submissions.each do |s|
|
|
assets = res.select{|a| a.asset == s}
|
|
expect(assets.length).to be > 0 #eql(2)
|
|
expect(assets.map{|a| a.assessor_id}.uniq.length).to eql(assets.length)
|
|
|
|
assessors = res.select{|a| a.assessor_asset == s}
|
|
expect(assessors.length).to eql(2)
|
|
expect(assessors[0].asset_id).not_to eql(assessors[1].asset_id)
|
|
end
|
|
end
|
|
|
|
it "should assign late peer reviews" do
|
|
@submissions = []
|
|
users = create_users_in_course(@course, 5.times.map{ |i| {name: "user #{i}"} }, return_type: :record)
|
|
users.each do |u|
|
|
#@a.context.reload
|
|
@submissions << @a.submit_homework(u, :submission_type => "online_url", :url => "http://www.google.com")
|
|
end
|
|
@a.peer_review_count = 2
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length * 2)
|
|
user = create_users_in_course(@course, [{name: "new user"}], return_type: :record).first
|
|
@a.reload
|
|
s = @a.submit_homework(user, :submission_type => "online_url", :url => "http://www.google.com")
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to be >= 2
|
|
expect(res.any?{|a| a.assessor_asset == s}).to eql(true)
|
|
end
|
|
|
|
it "should assign late peer reviews to each other if there is more than one" do
|
|
@a.reload
|
|
@submissions = []
|
|
users = create_users_in_course(@course, 10.times.map{ |i| {name: "user #{i}"} }, return_type: :record)
|
|
users.each do |u|
|
|
@submissions << @a.submit_homework(u, :submission_type => "online_url", :url => "http://www.google.com")
|
|
end
|
|
@a.peer_review_count = 2
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length * 2)
|
|
|
|
@late_submissions = []
|
|
users = create_users_in_course(@course, 3.times.map{ |i| {name: "user #{i}"} }, return_type: :record)
|
|
users.each do |u|
|
|
@late_submissions << @a.submit_homework(u, :submission_type => "online_url", :url => "http://www.google.com")
|
|
end
|
|
res = @a.assign_peer_reviews
|
|
expect(res.length).to be >= 6
|
|
ids = @late_submissions.map{|s| s.user_id}
|
|
end
|
|
|
|
context "differentiated_assignments" do
|
|
before :once do
|
|
setup_differentiated_assignments
|
|
@submissions = []
|
|
[@student1, @student2].each do |u|
|
|
@submissions << @assignment.submit_homework(u, :submission_type => "online_url", :url => "http://www.google.com")
|
|
end
|
|
end
|
|
context "feature on" do
|
|
it "should assign peer reviews only to students with visibility" do
|
|
@assignment.peer_review_count = 1
|
|
res = @assignment.assign_peer_reviews
|
|
expect(res.length).to eql(0)
|
|
@submissions.each do |s|
|
|
expect(res.map{|a| a.asset}).not_to be_include(s)
|
|
expect(res.map{|a| a.assessor_asset}).not_to be_include(s)
|
|
end
|
|
|
|
# once graded the student will have visibility
|
|
# and will therefore show up in the peer reviews
|
|
@assignment.grade_student(@student2, :grader => @teacher, :grade => '100')
|
|
|
|
res = @assignment.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length)
|
|
@submissions.each do |s|
|
|
expect(res.map{|a| a.asset}).to be_include(s)
|
|
expect(res.map{|a| a.assessor_asset}).to be_include(s)
|
|
end
|
|
end
|
|
|
|
end
|
|
context "feature off" do
|
|
before :once do
|
|
@course.disable_feature!(:differentiated_assignments)
|
|
@assignment.reload
|
|
end
|
|
|
|
it "should assign peer reviews to any student with a submission" do
|
|
@assignment.peer_review_count = 1
|
|
|
|
res = @assignment.assign_peer_reviews
|
|
expect(res.length).to eql(@submissions.length)
|
|
@submissions.each do |s|
|
|
expect(res.map{|a| a.asset}).to be_include(s)
|
|
expect(res.map{|a| a.assessor_asset}).to be_include(s)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "grading scales" do
|
|
before :once do
|
|
setup_assignment_without_submission
|
|
end
|
|
|
|
context "letter grades" do
|
|
before :once do
|
|
@assignment.update_attributes(:grading_type => 'letter_grade', :points_possible => 20)
|
|
end
|
|
|
|
it "should update grades when assignment changes" do
|
|
@enrollment = @student.enrollments.first
|
|
@assignment.reload
|
|
@sub = @assignment.grade_student(@student, :grader => @teacher, :grade => 'C').first
|
|
expect(@sub.grade).to eql('C')
|
|
expect(@sub.score).to eql(15.2)
|
|
expect(@enrollment.reload.computed_current_score).to eq 76
|
|
|
|
@assignment.points_possible = 30
|
|
@assignment.save!
|
|
@sub.reload
|
|
expect(@sub.score).to eql(15.2)
|
|
expect(@sub.grade).to eql('F')
|
|
expect(@enrollment.reload.computed_current_score).to eq 50.7
|
|
end
|
|
|
|
it "should accept lowercase letter grades" do
|
|
@assignment.reload
|
|
@sub = @assignment.grade_student(@student, :grader => @teacher, :grade => 'c').first
|
|
expect(@sub.grade).to eql('C')
|
|
expect(@sub.score).to eql(15.2)
|
|
end
|
|
end
|
|
|
|
context "gpa scale grades" do
|
|
before :once do
|
|
@assignment.update_attributes(:grading_type => 'gpa_scale', :points_possible => 20)
|
|
@course.grading_standards.build({title: "GPA"})
|
|
gs = @course.grading_standards.last
|
|
gs.data = {"4.0" => 0.94,
|
|
"3.7" => 0.90,
|
|
"3.3" => 0.87,
|
|
"3.0" => 0.84,
|
|
"2.7" => 0.80,
|
|
"2.3" => 0.77,
|
|
"2.0" => 0.74,
|
|
"1.7" => 0.70,
|
|
"1.3" => 0.67,
|
|
"1.0" => 0.64,
|
|
"0" => 0.01,
|
|
"M" => 0.0 }
|
|
gs.assignments << @a
|
|
gs.save!
|
|
end
|
|
|
|
it "should update grades when assignment changes" do
|
|
@enrollment = @student.enrollments.first
|
|
@assignment.reload
|
|
@sub = @assignment.grade_student(@student, :grader => @teacher, :grade => '2.0').first
|
|
expect(@sub.grade).to eql('2.0')
|
|
expect(@sub.score).to eql(15.2)
|
|
expect(@enrollment.reload.computed_current_score).to eq 76
|
|
|
|
@assignment.points_possible = 30
|
|
@assignment.save!
|
|
@sub.reload
|
|
expect(@sub.score).to eql(15.2)
|
|
expect(@sub.grade).to eql('0')
|
|
expect(@enrollment.reload.computed_current_score).to eq 50.7
|
|
end
|
|
|
|
it "should accept lowercase gpa grades" do
|
|
@assignment.reload
|
|
@sub = @assignment.grade_student(@student, :grader => @teacher, :grade => 'm').first
|
|
expect(@sub.grade).to eql('M')
|
|
expect(@sub.score).to eql(0.0)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "as_json" do
|
|
before :once do
|
|
assignment_model(course: @course)
|
|
end
|
|
|
|
it "should include permissions if specified" do
|
|
expect(@assignment.to_json).not_to match(/permissions/)
|
|
expect(@assignment.to_json(:permissions => {:user => nil})).to match(/\"permissions\"\s*:\s*\{/)
|
|
expect(@assignment.grants_right?(@teacher, :create)).to eql(true)
|
|
expect(@assignment.to_json(:permissions => {:user => @teacher, :session => nil})).to match(/\"permissions\"\s*:\s*\{\"/)
|
|
hash = @assignment.as_json(:permissions => {:user => @teacher, :session => nil})
|
|
expect(hash["assignment"]).not_to be_nil
|
|
expect(hash["assignment"]["permissions"]).not_to be_nil
|
|
expect(hash["assignment"]["permissions"]).not_to be_empty
|
|
expect(hash["assignment"]["permissions"]["read"]).to eql(true)
|
|
end
|
|
|
|
it "should serialize with roots included in nested elements" do
|
|
@course.assignments.create!(:title => "some assignment")
|
|
hash = @course.as_json(:include => :assignments)
|
|
expect(hash["course"]).not_to be_nil
|
|
expect(hash["course"]["assignments"]).not_to be_empty
|
|
expect(hash["course"]["assignments"][0]).not_to be_nil
|
|
expect(hash["course"]["assignments"][0]["assignment"]).not_to be_nil
|
|
end
|
|
|
|
it "should serialize with permissions" do
|
|
hash = @course.as_json(:permissions => {:user => @teacher, :session => nil} )
|
|
expect(hash["course"]).not_to be_nil
|
|
expect(hash["course"]["permissions"]).not_to be_nil
|
|
expect(hash["course"]["permissions"]).not_to be_empty
|
|
expect(hash["course"]["permissions"]["read"]).to eql(true)
|
|
end
|
|
|
|
it "should exclude root" do
|
|
hash = @course.as_json(:include_root => false, :permissions => {:user => @teacher, :session => nil} )
|
|
expect(hash["course"]).to be_nil
|
|
expect(hash["name"]).to eql(@course.name)
|
|
expect(hash["permissions"]).not_to be_nil
|
|
expect(hash["permissions"]).not_to be_empty
|
|
expect(hash["permissions"]["read"]).to eql(true)
|
|
end
|
|
|
|
it "should include group_category" do
|
|
assignment_model(:group_category => "Something", :course => @course)
|
|
hash = @assignment.as_json
|
|
expect(hash["assignment"]["group_category"]).to eq "Something"
|
|
end
|
|
end
|
|
|
|
context "ical" do
|
|
it ".to_ics should not fail for null due dates" do
|
|
assignment_model(:due_at => "", :course => @course)
|
|
res = @assignment.to_ics
|
|
expect(res).not_to be_nil
|
|
expect(res.match(/DTSTART/)).to be_nil
|
|
end
|
|
|
|
it ".to_ics should not return data for null due dates" do
|
|
assignment_model(:due_at => "", :course => @course)
|
|
res = @assignment.to_ics(false)
|
|
expect(res).to be_nil
|
|
end
|
|
|
|
it ".to_ics should return string data for assignments with due dates" do
|
|
Time.zone = 'UTC'
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :course => @course)
|
|
# force known value so we can check serialization
|
|
@assignment.updated_at = Time.at(1220443500) # 3 Sep 2008 12:05pm (UTC)
|
|
res = @assignment.to_ics
|
|
expect(res).not_to be_nil
|
|
expect(res.match(/DTEND:20080903T115500Z/)).not_to be_nil
|
|
expect(res.match(/DTSTART:20080903T115500Z/)).not_to be_nil
|
|
expect(res.match(/DTSTAMP:20080903T120500Z/)).not_to be_nil
|
|
end
|
|
|
|
it ".to_ics should return string data for assignments with due dates in correct tz" do
|
|
Time.zone = 'Alaska' # -0800
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :course => @course)
|
|
# force known value so we can check serialization
|
|
@assignment.updated_at = Time.at(1220472300) # 3 Sep 2008 12:05pm (AKDT)
|
|
res = @assignment.to_ics
|
|
expect(res).not_to be_nil
|
|
expect(res.match(/DTEND:20080903T195500Z/)).not_to be_nil
|
|
expect(res.match(/DTSTART:20080903T195500Z/)).not_to be_nil
|
|
expect(res.match(/DTSTAMP:20080903T200500Z/)).not_to be_nil
|
|
end
|
|
|
|
it ".to_ics should return data for assignments with due dates" do
|
|
Time.zone = 'UTC'
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :course => @course)
|
|
# force known value so we can check serialization
|
|
@assignment.updated_at = Time.at(1220443500) # 3 Sep 2008 12:05pm (UTC)
|
|
res = @assignment.to_ics(false)
|
|
expect(res).not_to be_nil
|
|
expect(res.start.icalendar_tzid).to eq 'UTC'
|
|
expect(res.start.strftime('%Y-%m-%dT%H:%M:%S')).to eq Time.zone.parse("Sep 3 2008 11:55am").in_time_zone('UTC').strftime('%Y-%m-%dT%H:%M:00')
|
|
expect(res.end.icalendar_tzid).to eq 'UTC'
|
|
expect(res.end.strftime('%Y-%m-%dT%H:%M:%S')).to eq Time.zone.parse("Sep 3 2008 11:55am").in_time_zone('UTC').strftime('%Y-%m-%dT%H:%M:00')
|
|
expect(res.dtstamp.icalendar_tzid).to eq 'UTC'
|
|
expect(res.dtstamp.strftime('%Y-%m-%dT%H:%M:%S')).to eq Time.zone.parse("Sep 3 2008 12:05pm").in_time_zone('UTC').strftime('%Y-%m-%dT%H:%M:00')
|
|
end
|
|
|
|
it ".to_ics should return data for assignments with due dates in correct tz" do
|
|
Time.zone = 'Alaska' # -0800
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :course => @course)
|
|
# force known value so we can check serialization
|
|
@assignment.updated_at = Time.at(1220472300) # 3 Sep 2008 12:05pm (AKDT)
|
|
res = @assignment.to_ics(false)
|
|
expect(res).not_to be_nil
|
|
expect(res.start.icalendar_tzid).to eq 'UTC'
|
|
expect(res.start.strftime('%Y-%m-%dT%H:%M:%S')).to eq Time.zone.parse("Sep 3 2008 11:55am").in_time_zone('UTC').strftime('%Y-%m-%dT%H:%M:00')
|
|
expect(res.end.icalendar_tzid).to eq 'UTC'
|
|
expect(res.end.strftime('%Y-%m-%dT%H:%M:%S')).to eq Time.zone.parse("Sep 3 2008 11:55am").in_time_zone('UTC').strftime('%Y-%m-%dT%H:%M:00')
|
|
expect(res.dtstamp.icalendar_tzid).to eq 'UTC'
|
|
expect(res.dtstamp.strftime('%Y-%m-%dT%H:%M:%S')).to eq Time.zone.parse("Sep 3 2008 12:05pm").in_time_zone('UTC').strftime('%Y-%m-%dT%H:%M:00')
|
|
end
|
|
|
|
it ".to_ics should return string dates for all_day events" do
|
|
Time.zone = 'UTC'
|
|
assignment_model(:due_at => "Sep 3 2008 11:59pm", :course => @course)
|
|
expect(@assignment.all_day).to eql(true)
|
|
res = @assignment.to_ics
|
|
expect(res.match(/DTSTART;VALUE=DATE:20080903/)).not_to be_nil
|
|
expect(res.match(/DTEND;VALUE=DATE:20080903/)).not_to be_nil
|
|
end
|
|
|
|
it ".to_ics should return a plain-text description and alt html description" do
|
|
html = %{<div>
|
|
This assignment is due December 16th. Plz discuss the reading.
|
|
<p> </p>
|
|
<p>Test.</p>
|
|
</div>}
|
|
assignment_model(:due_at => "Sep 3 2008 12:00am", :description => html, :course => @course)
|
|
ev = @assignment.to_ics(false)
|
|
skip("assignment description disabled")
|
|
expect(ev.description).to eq "This assignment is due December 16th. Plz discuss the reading.\n \n\n\n Test."
|
|
expect(ev.x_alt_desc).to eq html.strip
|
|
end
|
|
|
|
it ".to_ics should run the description through api_user_content to translate links" do
|
|
html = %{<a href="/calendar">Click!</a>}
|
|
assignment_model(:due_at => "Sep 3 2008 12:00am", :description => html, :course => @course)
|
|
ev = @assignment.to_ics(false)
|
|
skip("assignment description disabled")
|
|
expect(ev.description).to eq "[Click!](http://localhost/calendar)"
|
|
expect(ev.x_alt_desc).to eq %{<a href="http://localhost/calendar">Click!</a>}
|
|
end
|
|
|
|
it ".to_ics should populate uid and summary fields" do
|
|
Time.zone = 'UTC'
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :title => "assignment title", :course => @course)
|
|
ev = @a.to_ics(false)
|
|
expect(ev.uid).to eq "event-assignment-#{@a.id}"
|
|
expect(ev.summary).to eq "#{@a.title} [#{@a.context.course_code}]"
|
|
# TODO: ev.url.should == ?
|
|
end
|
|
|
|
it ".to_ics should apply due_at override information" do
|
|
Time.zone = 'UTC'
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :title => "assignment title", :course => @course)
|
|
@override = @a.assignment_overrides.build
|
|
@override.set = @course.default_section
|
|
@override.override_due_at(Time.zone.parse("Sep 28 2008 11:55am"))
|
|
@override.save!
|
|
|
|
assignment = AssignmentOverrideApplicator.assignment_with_overrides(@a, [@override])
|
|
ev = assignment.to_ics(false)
|
|
expect(ev.uid).to eq "event-assignment-override-#{@override.id}"
|
|
expect(ev.summary).to eq "#{@a.title} (#{@override.title}) [#{assignment.context.course_code}]"
|
|
#TODO: ev.url.should == ?
|
|
end
|
|
|
|
it ".to_ics should not apply non-due_at override information" do
|
|
Time.zone = 'UTC'
|
|
assignment_model(:due_at => "Sep 3 2008 11:55am", :title => "assignment title", :course => @course)
|
|
@override = @a.assignment_overrides.build
|
|
@override.set = @course.default_section
|
|
@override.override_lock_at(Time.zone.parse("Sep 28 2008 11:55am"))
|
|
@override.save!
|
|
|
|
assignment = AssignmentOverrideApplicator.assignment_with_overrides(@a, [@override])
|
|
ev = assignment.to_ics(false)
|
|
expect(ev.uid).to eq "event-assignment-#{@a.id}"
|
|
expect(ev.summary).to eq "#{@a.title} [#{@a.context.course_code}]"
|
|
end
|
|
|
|
end
|
|
|
|
context "quizzes" do
|
|
before :once do
|
|
assignment_model(:submission_types => "online_quiz", :course => @course)
|
|
end
|
|
|
|
it "should create a quiz if none exists and specified" do
|
|
@a.reload
|
|
expect(@a.submission_types).to eql('online_quiz')
|
|
expect(@a.quiz).not_to be_nil
|
|
expect(@a.quiz.assignment_id).to eql(@a.id)
|
|
@a.due_at = Time.now
|
|
@a.save
|
|
@a.reload
|
|
expect(@a.quiz).not_to be_nil
|
|
expect(@a.quiz.assignment_id).to eql(@a.id)
|
|
end
|
|
|
|
it "should delete a quiz if no longer specified" do
|
|
@a.reload
|
|
expect(@a.submission_types).to eql('online_quiz')
|
|
expect(@a.quiz).not_to be_nil
|
|
expect(@a.quiz.assignment_id).to eql(@a.id)
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@a.reload
|
|
expect(@a.quiz).to be_nil
|
|
end
|
|
|
|
it "should not delete the assignment when unlinked from a quiz" do
|
|
@a.reload
|
|
expect(@a.submission_types).to eql('online_quiz')
|
|
@quiz = @a.quiz
|
|
@quiz.unpublish!
|
|
expect(@quiz).not_to be_nil
|
|
expect(@quiz.state).to eql(:unpublished)
|
|
expect(@quiz.assignment_id).to eql(@a.id)
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@quiz = Quizzes::Quiz.find(@quiz.id)
|
|
expect(@quiz.assignment_id).to eql(nil)
|
|
expect(@quiz.state).to eql(:deleted)
|
|
@a.reload
|
|
expect(@a.quiz).to be_nil
|
|
expect(@a.state).to eql(:unpublished)
|
|
end
|
|
|
|
it "should not delete the quiz if non-empty when unlinked" do
|
|
@a.reload
|
|
expect(@a.submission_types).to eql('online_quiz')
|
|
@quiz = @a.quiz
|
|
expect(@quiz).not_to be_nil
|
|
expect(@quiz.assignment_id).to eql(@a.id)
|
|
@quiz.quiz_questions.create!()
|
|
@quiz.generate_quiz_data
|
|
@quiz.save!
|
|
@a.quiz.reload
|
|
expect(@quiz.root_entries).not_to be_empty
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@a.reload
|
|
expect(@a.quiz).to be_nil
|
|
expect(@a.state).to eql(:published)
|
|
@quiz = Quizzes::Quiz.find(@quiz.id)
|
|
expect(@quiz.assignment_id).to eql(nil)
|
|
expect(@quiz.state).to eql(:available)
|
|
end
|
|
|
|
it "should grab the original quiz if unlinked and relinked" do
|
|
@a.reload
|
|
expect(@a.submission_types).to eql('online_quiz')
|
|
@quiz = @a.quiz
|
|
expect(@quiz).not_to be_nil
|
|
expect(@quiz.assignment_id).to eql(@a.id)
|
|
@a.quiz.reload
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@a.submission_types = 'online_quiz'
|
|
@a.save!
|
|
@a.reload
|
|
expect(@a.quiz).to eql(@quiz)
|
|
expect(@a.state).to eql(:published)
|
|
@quiz.reload
|
|
expect(@quiz.state).to eql(:available)
|
|
end
|
|
|
|
it "updates the draft state of its associated quiz" do
|
|
@a.reload
|
|
@a.publish
|
|
@a.save!
|
|
expect(@a.quiz.reload).to be_published
|
|
@a.unpublish
|
|
expect(@a.quiz.reload).not_to be_published
|
|
end
|
|
end
|
|
|
|
context "topics" do
|
|
before :once do
|
|
assignment_model(:course => @course, :submission_types => "discussion_topic", :updating_user => @teacher)
|
|
end
|
|
|
|
it "should create a discussion_topic if none exists and specified" do
|
|
expect(@a.submission_types).to eql('discussion_topic')
|
|
expect(@a.discussion_topic).not_to be_nil
|
|
expect(@a.discussion_topic.assignment_id).to eql(@a.id)
|
|
expect(@a.discussion_topic.user_id).to eql(@teacher.id)
|
|
@a.due_at = Time.now
|
|
@a.save
|
|
@a.reload
|
|
expect(@a.discussion_topic).not_to be_nil
|
|
expect(@a.discussion_topic.assignment_id).to eql(@a.id)
|
|
expect(@a.discussion_topic.user_id).to eql(@teacher.id)
|
|
end
|
|
|
|
it "should delete a discussion_topic if no longer specified" do
|
|
expect(@a.submission_types).to eql('discussion_topic')
|
|
expect(@a.discussion_topic).not_to be_nil
|
|
expect(@a.discussion_topic.assignment_id).to eql(@a.id)
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@a.reload
|
|
expect(@a.discussion_topic).to be_nil
|
|
end
|
|
|
|
it "should not delete the assignment when unlinked from a topic" do
|
|
expect(@a.submission_types).to eql('discussion_topic')
|
|
@topic = @a.discussion_topic
|
|
expect(@topic).not_to be_nil
|
|
expect(@topic.state).to eql(:active)
|
|
expect(@topic.assignment_id).to eql(@a.id)
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@topic = DiscussionTopic.find(@topic.id)
|
|
expect(@topic.assignment_id).to eql(nil)
|
|
expect(@topic.state).to eql(:deleted)
|
|
@a.reload
|
|
expect(@a.discussion_topic).to be_nil
|
|
expect(@a.state).to eql(:published)
|
|
end
|
|
|
|
it "should not delete the topic if non-empty when unlinked" do
|
|
expect(@a.submission_types).to eql('discussion_topic')
|
|
@topic = @a.discussion_topic
|
|
expect(@topic).not_to be_nil
|
|
expect(@topic.assignment_id).to eql(@a.id)
|
|
@topic.discussion_entries.create!(:user => @user, :message => "testing")
|
|
@a.discussion_topic.reload
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@a.reload
|
|
expect(@a.discussion_topic).to be_nil
|
|
expect(@a.state).to eql(:published)
|
|
@topic = DiscussionTopic.find(@topic.id)
|
|
expect(@topic.assignment_id).to eql(nil)
|
|
expect(@topic.state).to eql(:active)
|
|
end
|
|
|
|
it "should grab the original topic if unlinked and relinked" do
|
|
expect(@a.submission_types).to eql('discussion_topic')
|
|
@topic = @a.discussion_topic
|
|
expect(@topic).not_to be_nil
|
|
expect(@topic.assignment_id).to eql(@a.id)
|
|
@topic.discussion_entries.create!(:user => @user, :message => "testing")
|
|
@a.discussion_topic.reload
|
|
@a.submission_types = 'on_paper'
|
|
@a.save!
|
|
@a.submission_types = 'discussion_topic'
|
|
@a.save!
|
|
@a.reload
|
|
expect(@a.discussion_topic).to eql(@topic)
|
|
expect(@a.state).to eql(:published)
|
|
@topic.reload
|
|
expect(@topic.state).to eql(:active)
|
|
end
|
|
end
|
|
|
|
context "participants" do
|
|
describe "with differentiated_assignments on" do
|
|
before :once do
|
|
setup_differentiated_assignments(ta: true)
|
|
end
|
|
|
|
it 'returns users with visibility' do
|
|
expect(@assignment.participants.length).to eq(4) #teacher, TA, 2 students
|
|
end
|
|
|
|
it 'includes students with visibility' do
|
|
expect(@assignment.participants.include?(@student1)).to be_truthy
|
|
end
|
|
|
|
it 'excludes students without visibility' do
|
|
expect(@assignment.participants.include?(@student2)).to be_falsey
|
|
end
|
|
|
|
it 'includes admins with visibility' do
|
|
expect(@assignment.participants.include?(@teacher)).to be_truthy
|
|
expect(@assignment.participants.include?(@ta)).to be_truthy
|
|
end
|
|
|
|
context "including observers" do
|
|
before do
|
|
oe = @assignment.context.enroll_user(user_with_pseudonym(active_all: true), 'ObserverEnrollment',:enrollment_state => 'active')
|
|
@course_level_observer = oe.user
|
|
|
|
oe = @assignment.context.enroll_user(user_with_pseudonym(active_all: true), 'ObserverEnrollment',:enrollment_state => 'active')
|
|
oe.associated_user_id = @student1.id
|
|
oe.save!
|
|
@student1_observer = oe.user
|
|
|
|
oe = @assignment.context.enroll_user(user_with_pseudonym(active_all: true), 'ObserverEnrollment',:enrollment_state => 'active')
|
|
oe.associated_user_id = @student2.id
|
|
oe.save!
|
|
@student2_observer = oe.user
|
|
end
|
|
|
|
it "should include course_level observers" do
|
|
expect(@assignment.participants(include_observers: true).include?(@course_level_observer)).to be_truthy
|
|
end
|
|
|
|
it "should exclude student observers if their student does not have visibility" do
|
|
expect(@assignment.participants(include_observers: true).include?(@student1_observer)).to be_truthy
|
|
expect(@assignment.participants(include_observers: true).include?(@student2_observer)).to be_falsey
|
|
end
|
|
|
|
it "should exclude all observers unless opt is given" do
|
|
expect(@assignment.participants.include?(@student1_observer)).to be_falsey
|
|
expect(@assignment.participants.include?(@student2_observer)).to be_falsey
|
|
expect(@assignment.participants.include?(@course_level_observer)).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "with differentiated_assignments off" do
|
|
before :once do
|
|
setup_differentiated_assignments
|
|
@course.disable_feature!(:differentiated_assignments)
|
|
end
|
|
|
|
it 'normally returns all users in the course' do
|
|
expect(@assignment.participants.length).to eq 4
|
|
end
|
|
|
|
it "should exclude students when given the option" do
|
|
expect( @assignment.participants(excluded_user_ids: [@student1.id]) ).to_not be_include(@student1)
|
|
expect( @assignment.participants(excluded_user_ids: [@student1.id]) ).to be_include(@student2)
|
|
end
|
|
|
|
context "including observers" do
|
|
before :once do
|
|
oe = @assignment.context.enroll_user(user_with_pseudonym, 'ObserverEnrollment',:enrollment_state => 'active')
|
|
@course_level_observer = oe.user
|
|
|
|
oe = @assignment.context.enroll_user(user_with_pseudonym, 'ObserverEnrollment',:enrollment_state => 'active')
|
|
oe.associated_user_id = @student1.id
|
|
oe.save!
|
|
@student1_observer = oe.user
|
|
end
|
|
it "should include course_level observers" do
|
|
expect( @assignment.participants(include_observers: true) ).to be_include(@course_level_observer)
|
|
end
|
|
it "should include student observers if their student is participating" do
|
|
expect( @assignment.participants(include_observers: true) ).to be_include(@student1)
|
|
expect( @assignment.participants(include_observers: true) ).to be_include(@student1_observer)
|
|
end
|
|
it "should exclude student observers if their student is explicitly excluded" do
|
|
expect( @assignment.participants(include_observers: true, excluded_user_ids: [@student1.id]) ).to_not be_include(@student1)
|
|
expect( @assignment.participants(include_observers: true, excluded_user_ids: [@student1.id]) ).to_not be_include(@student1_observer)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "broadcast policy" do
|
|
context "due date changed" do
|
|
before :once do
|
|
Notification.create(:name => 'Assignment Due Date Changed')
|
|
end
|
|
|
|
it "should create a message when an assignment due date has changed" do
|
|
assignment_model(:title => 'Assignment with unstable due date', :course => @course)
|
|
@a.created_at = 1.month.ago
|
|
@a.due_at = Time.now + 60
|
|
@a.save!
|
|
expect(@a.messages_sent).to be_include('Assignment Due Date Changed')
|
|
end
|
|
|
|
it "should NOT create a message when everything but the assignment due date has changed" do
|
|
t = Time.parse("Sep 1, 2009 5:00pm")
|
|
assignment_model(:title => 'Assignment with unstable due date', :due_at => t, :course => @course)
|
|
expect(@a.due_at).to eql(t)
|
|
@a.submission_types = "online_url"
|
|
@a.title = "New Title"
|
|
@a.due_at = t + 1
|
|
@a.description = "New description"
|
|
@a.points_possible = 50
|
|
@a.save!
|
|
expect(@a.messages_sent).not_to be_include('Assignment Due Date Changed')
|
|
end
|
|
end
|
|
|
|
context "assignment graded" do
|
|
before(:once) { setup_assignment_with_students }
|
|
|
|
specify { expect(@assignment).to be_published }
|
|
|
|
it "should notify students when their grade is changed" do
|
|
@sub2 = @assignment.grade_student(@stu2, :grade => 8).first
|
|
expect(@sub2.messages_sent).not_to be_empty
|
|
expect(@sub2.messages_sent['Submission Graded']).not_to be_nil
|
|
expect(@sub2.messages_sent['Submission Grade Changed']).to be_nil
|
|
@sub2.update_attributes(:graded_at => Time.now - 60*60)
|
|
@sub2 = @assignment.grade_student(@stu2, :grade => 9).first
|
|
expect(@sub2.messages_sent).not_to be_empty
|
|
expect(@sub2.messages_sent['Submission Graded']).to be_nil
|
|
expect(@sub2.messages_sent['Submission Grade Changed']).not_to be_nil
|
|
end
|
|
|
|
it "should notify affected students on a mass-grade change" do
|
|
skip "CNVS-5969 - Setting a default grade should send a 'Submission Graded' notification"
|
|
@assignment.set_default_grade(:default_grade => 10)
|
|
msg_sub1 = @assignment.submissions.detect{|s| s.id = @sub1.id}
|
|
expect(msg_sub1.messages_sent).not_to be_nil
|
|
expect(msg_sub1.messages_sent['Submission Grade Changed']).not_to be_nil
|
|
msg_sub2 = @assignment.submissions.detect{|s| s.id = @sub2.id}
|
|
expect(msg_sub2.messages_sent).not_to be_nil
|
|
expect(msg_sub2.messages_sent['Submission Graded']).not_to be_nil
|
|
end
|
|
|
|
describe 'while they are muted' do
|
|
before(:once) { @assignment.mute! }
|
|
|
|
specify { expect(@assignment).to be_muted }
|
|
|
|
it "should not notify affected students on a mass-grade change if muted" do
|
|
skip "CNVS-5969 - Setting a default grade should send a 'Submission Graded' notification"
|
|
@assignment.set_default_grade(:default_grade => 10)
|
|
expect(@assignment.messages_sent).to be_empty
|
|
end
|
|
|
|
it "should not notify students when their grade is changed if muted" do
|
|
@sub2 = @assignment.grade_student(@stu2, :grade => 8).first
|
|
@sub2.update_attributes(:graded_at => Time.now - 60*60)
|
|
@sub2 = @assignment.grade_student(@stu2, :grade => 9).first
|
|
expect(@sub2.messages_sent).to be_empty
|
|
end
|
|
end
|
|
|
|
it "should include re-submitted submissions in the list of submissions needing grading" do
|
|
expect(@assignment).to be_published
|
|
expect(@assignment.submissions.size).to eq 1
|
|
expect(Assignment.need_grading_info(15).where(id: @assignment).first).to be_nil
|
|
@assignment.submit_homework(@stu1, :body => "Changed my mind!")
|
|
@sub1.reload
|
|
expect(@sub1.body).to eq "Changed my mind!"
|
|
expect(Assignment.need_grading_info(15).where(id: @assignment).first).not_to be_nil
|
|
end
|
|
end
|
|
|
|
context "assignment changed" do
|
|
before :once do
|
|
Notification.create(:name => 'Assignment Changed')
|
|
assignment_model(course: @course)
|
|
end
|
|
|
|
it "should create a message when an assigment changes after it's been published" do
|
|
@a.created_at = Time.parse("Jan 2 2000")
|
|
@a.description = "something different"
|
|
@a.notify_of_update = true
|
|
@a.save
|
|
expect(@a.messages_sent).to be_include('Assignment Changed')
|
|
end
|
|
|
|
it "should NOT create a message when an assigment changes SHORTLY AFTER it's been created" do
|
|
@a.description = "something different"
|
|
@a.save
|
|
expect(@a.messages_sent).not_to be_include('Assignment Changed')
|
|
end
|
|
|
|
it "should not create a message when a muted assignment changes" do
|
|
@a.mute!
|
|
@a = Assignment.find(@a.id) # blank slate for messages_sent
|
|
@a.description = "something different"
|
|
@a.save
|
|
expect(@a.messages_sent).to be_empty
|
|
end
|
|
end
|
|
|
|
context "assignment created" do
|
|
before :once do
|
|
Notification.create(:name => 'Assignment Created')
|
|
end
|
|
|
|
it "should create a message when an assigment is added to a course in process" do
|
|
assignment_model(:course => @course)
|
|
expect(@a.messages_sent).to be_include('Assignment Created')
|
|
end
|
|
|
|
it "should not create a message in an unpublished course" do
|
|
Notification.create(:name => 'Assignment Created')
|
|
course_with_teacher(:active_user => true)
|
|
assignment_model(:course => @course)
|
|
expect(@a.messages_sent).not_to be_include('Assignment Created')
|
|
end
|
|
end
|
|
|
|
context "varied due date notifications" do
|
|
before :once do
|
|
@teacher.communication_channels.create(:path => "teacher@instructure.com").confirm!
|
|
|
|
@studentA = user_with_pseudonym(:active_all => true, :name => 'StudentA', :username => 'studentA@instructure.com')
|
|
@ta = user_with_pseudonym(:active_all => true, :name => 'TA1', :username => 'ta1@instructure.com')
|
|
@course.enroll_student(@studentA).update_attribute(:workflow_state, 'active')
|
|
@course.enroll_user(@ta, 'TaEnrollment', :enrollment_state => 'active', :limit_privileges_to_course_section => true)
|
|
|
|
@section2 = @course.course_sections.create!(:name => 'section 2')
|
|
@studentB = user_with_pseudonym(:active_all => true, :name => 'StudentB', :username => 'studentB@instructure.com')
|
|
@ta2 = user_with_pseudonym(:active_all => true, :name => 'TA2', :username => 'ta2@instructure.com')
|
|
@section2.enroll_user(@studentB, 'StudentEnrollment', 'active')
|
|
@course.enroll_user(@ta2, 'TaEnrollment', :section => @section2, :enrollment_state => 'active', :limit_privileges_to_course_section => true)
|
|
|
|
Time.zone = 'Alaska'
|
|
default_due = DateTime.parse("01 Jan 2011 14:00 AKST")
|
|
section_2_due = DateTime.parse("02 Jan 2011 14:00 AKST")
|
|
@assignment = @course.assignments.build(:title => "some assignment", :due_at => default_due, :submission_types => ['online_text_entry'])
|
|
@assignment.save_without_broadcasting!
|
|
override = @assignment.assignment_overrides.build
|
|
override.set = @section2
|
|
override.override_due_at(section_2_due)
|
|
override.save!
|
|
end
|
|
|
|
context "assignment created" do
|
|
before :once do
|
|
Notification.create(:name => 'Assignment Created')
|
|
end
|
|
|
|
it "should notify of the correct due date for the recipient, or 'multiple'" do
|
|
@assignment.do_notifications!
|
|
|
|
messages_sent = @assignment.messages_sent['Assignment Created']
|
|
expect(messages_sent.detect{|m|m.user_id == @teacher.id}.body).to be_include "Multiple Dates"
|
|
expect(messages_sent.detect{|m|m.user_id == @studentA.id}.body).to be_include "Jan 1, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @ta.id}.body).to be_include "Multiple Dates"
|
|
expect(messages_sent.detect{|m|m.user_id == @studentB.id}.body).to be_include "Jan 2, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @ta2.id}.body).to be_include "Multiple Dates"
|
|
end
|
|
|
|
it "should notify the correct people with differentiated_assignments enabled" do
|
|
@assignment.context.root_account.enable_feature!(:differentiated_assignments)
|
|
section = @course.course_sections.create!(name: 'Lonely Section')
|
|
student = student_in_section(section)
|
|
@assignment.do_notifications!
|
|
|
|
messages_sent = @assignment.messages_sent['Assignment Created']
|
|
expect(messages_sent.detect{|m|m.user_id == @teacher.id}.body).to be_include "Multiple Dates"
|
|
expect(messages_sent.detect{|m|m.user_id == @studentA.id}.body).to be_include "Jan 1, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @ta.id}.body).to be_include "Multiple Dates"
|
|
expect(messages_sent.detect{|m|m.user_id == @studentB.id}.body).to be_include "Jan 2, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @ta2.id}.body).to be_include "Multiple Dates"
|
|
expect(messages_sent.detect{|m|m.user_id == student.id}).to be_nil
|
|
end
|
|
|
|
it "should collapse identical instructor due dates" do
|
|
# change the override to match the default due date
|
|
override = @assignment.assignment_overrides.first
|
|
override.override_due_at(@assignment.due_at)
|
|
override.save!
|
|
@assignment.do_notifications!
|
|
|
|
# when the override matches the default, show the default and not "Multiple"
|
|
messages_sent = @assignment.messages_sent['Assignment Created']
|
|
messages_sent.each{|m| expect(m.body).to be_include "Jan 1, 2011"}
|
|
end
|
|
end
|
|
|
|
context "assignment due date changed" do
|
|
before :once do
|
|
Notification.create(:name => 'Assignment Due Date Changed')
|
|
Notification.create(:name => 'Assignment Due Date Override Changed')
|
|
end
|
|
|
|
it "should notify appropriate parties when the default due date changes" do
|
|
@assignment.update_attribute(:created_at, 1.day.ago)
|
|
|
|
@assignment.due_at = DateTime.parse("09 Jan 2011 14:00 AKST")
|
|
@assignment.save!
|
|
|
|
messages_sent = @assignment.messages_sent['Assignment Due Date Changed']
|
|
expect(messages_sent.detect{|m|m.user_id == @teacher.id}.body).to be_include "Jan 9, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @studentA.id}.body).to be_include "Jan 9, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @ta.id}.body).to be_include "Jan 9, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @studentB.id}).to be_nil
|
|
expect(messages_sent.detect{|m|m.user_id == @ta2.id}.body).to be_include "Jan 9, 2011"
|
|
end
|
|
|
|
it "should notify appropriate parties when an override due date changes" do
|
|
@assignment.update_attribute(:created_at, 1.day.ago)
|
|
|
|
override = @assignment.assignment_overrides.first.reload
|
|
override.override_due_at(DateTime.parse("11 Jan 2011 11:11 AKST"))
|
|
override.save!
|
|
|
|
messages_sent = override.messages_sent['Assignment Due Date Changed']
|
|
expect(messages_sent.detect{|m|m.user_id == @studentA.id}).to be_nil
|
|
expect(messages_sent.detect{|m|m.user_id == @studentB.id}.body).to be_include "Jan 11, 2011"
|
|
|
|
messages_sent = override.messages_sent['Assignment Due Date Override Changed']
|
|
expect(messages_sent.detect{|m|m.user_id == @ta.id}).to be_nil
|
|
expect(messages_sent.detect{|m|m.user_id == @teacher.id}.body).to be_include "Jan 11, 2011"
|
|
expect(messages_sent.detect{|m|m.user_id == @ta2.id}.body).to be_include "Jan 11, 2011"
|
|
end
|
|
end
|
|
|
|
context "assignment submitted late" do
|
|
before :once do
|
|
Notification.create(:name => 'Assignment Submitted')
|
|
Notification.create(:name => 'Assignment Submitted Late')
|
|
end
|
|
|
|
it "should send a late submission notification iff the submit date is late for the submitter" do
|
|
fake_submission_time = Time.parse "Jan 01 17:00:00 -0900 2011"
|
|
Time.stubs(:now).returns(fake_submission_time)
|
|
subA = @assignment.submit_homework @studentA, :submission_type => "online_text_entry", :body => "ooga"
|
|
subB = @assignment.submit_homework @studentB, :submission_type => "online_text_entry", :body => "booga"
|
|
Time.unstub(:now)
|
|
|
|
expect(subA.messages_sent["Assignment Submitted Late"]).not_to be_nil
|
|
expect(subB.messages_sent["Assignment Submitted Late"]).to be_nil
|
|
end
|
|
end
|
|
|
|
context "group assignment submitted late" do
|
|
before :once do
|
|
Notification.create(:name => 'Group Assignment Submitted Late')
|
|
end
|
|
|
|
it "should send a late submission notification iff the submit date is late for the group" do
|
|
@a = assignment_model(:course => @course, :group_category => "Study Groups", :due_at => Time.parse("Jan 01 17:00:00 -0900 2011"), :submission_types => ["online_text_entry"])
|
|
@group1 = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)
|
|
@group1.add_user(@studentA)
|
|
@group2 = @a.context.groups.create!(:name => "Study Group 2", :group_category => @a.group_category)
|
|
@group2.add_user(@studentB)
|
|
override = @a.assignment_overrides.new
|
|
override.set = @group2
|
|
override.override_due_at(Time.parse("Jan 03 17:00:00 -0900 2011"))
|
|
override.save!
|
|
fake_submission_time = Time.parse("Jan 02 17:00:00 -0900 2011")
|
|
Time.stubs(:now).returns(fake_submission_time)
|
|
subA = @assignment.submit_homework @studentA, :submission_type => "online_text_entry", :body => "eenie"
|
|
subB = @assignment.submit_homework @studentB, :submission_type => "online_text_entry", :body => "meenie"
|
|
Time.unstub(:now)
|
|
|
|
expect(subA.messages_sent["Group Assignment Submitted Late"]).not_to be_nil
|
|
expect(subB.messages_sent["Group Assignment Submitted Late"]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "group assignment" do
|
|
before :once do
|
|
setup_assignment_with_group
|
|
end
|
|
|
|
it "should submit the homework for all students in the same group" do
|
|
sub = @a.submit_homework(@u1, :submission_type => "online_text_entry", :body => "Some text for you")
|
|
expect(sub.user_id).to eql(@u1.id)
|
|
@a.reload
|
|
subs = @a.submissions
|
|
expect(subs.length).to eql(2)
|
|
expect(subs.map(&:group_id).uniq).to eql([@group.id])
|
|
expect(subs.map(&:submission_type).uniq).to eql(['online_text_entry'])
|
|
expect(subs.map(&:body).uniq).to eql(['Some text for you'])
|
|
end
|
|
|
|
it "should submit the homework for all students in the group if grading them individually" do
|
|
@a.update_attribute(:grade_group_students_individually, true)
|
|
res = @a.submit_homework(@u1, :submission_type => "online_text_entry", :body => "Test submission")
|
|
@a.reload
|
|
submissions = @a.submissions
|
|
expect(submissions.length).to eql 2
|
|
expect(submissions.map(&:group_id).uniq).to eql [@group.id]
|
|
expect(submissions.map(&:submission_type).uniq).to eql ["online_text_entry"]
|
|
expect(submissions.map(&:body).uniq).to eql ["Test submission"]
|
|
end
|
|
|
|
it "should update submission for all students in the same group" do
|
|
res = @a.grade_student(@u1, :grade => "10")
|
|
expect(res).not_to be_nil
|
|
expect(res).not_to be_empty
|
|
expect(res.length).to eql(2)
|
|
expect(res.map{|s| s.user}).to be_include(@u1)
|
|
expect(res.map{|s| s.user}).to be_include(@u2)
|
|
end
|
|
|
|
it "should create an initial submission comment for only the submitter by default" do
|
|
sub = @a.submit_homework(@u1, :submission_type => "online_text_entry", :body => "Some text for you", :comment => "hey teacher, i hate my group. i did this entire project by myself :(")
|
|
expect(sub.user_id).to eql(@u1.id)
|
|
expect(sub.submission_comments.size).to eql 1
|
|
@a.reload
|
|
other_sub = (@a.submissions - [sub])[0]
|
|
expect(other_sub.submission_comments.size).to eql 0
|
|
end
|
|
|
|
it "should add a submission comment for only the specified user by default" do
|
|
res = @a.grade_student(@u1, :comment => "woot")
|
|
expect(res).not_to be_nil
|
|
expect(res).not_to be_empty
|
|
expect(res.length).to eql(1)
|
|
expect(res.find{|s| s.user == @u1}.submission_comments).not_to be_empty
|
|
expect(res.find{|s| s.user == @u2}).to be_nil #.submission_comments.should be_empty
|
|
end
|
|
|
|
it "should update submission for only the individual student if set thay way" do
|
|
@a.update_attribute(:grade_group_students_individually, true)
|
|
res = @a.grade_student(@u1, :grade => "10")
|
|
expect(res).not_to be_nil
|
|
expect(res).not_to be_empty
|
|
expect(res.length).to eql(1)
|
|
expect(res[0].user).to eql(@u1)
|
|
end
|
|
|
|
it "should create an initial submission comment for all group members if specified" do
|
|
sub = @a.submit_homework(@u1, :submission_type => "online_text_entry", :body => "Some text for you", :comment => "ohai teacher, we had so much fun working together", :group_comment => "1")
|
|
expect(sub.user_id).to eql(@u1.id)
|
|
expect(sub.submission_comments.size).to eql 1
|
|
@a.reload
|
|
other_sub = (@a.submissions - [sub])[0]
|
|
expect(other_sub.submission_comments.size).to eql 1
|
|
end
|
|
|
|
it "should add a submission comment for all group members if specified" do
|
|
res = @a.grade_student(@u1, :comment => "woot", :group_comment => "1")
|
|
expect(res).not_to be_nil
|
|
expect(res).not_to be_empty
|
|
expect(res.length).to eql(2)
|
|
expect(res.find{|s| s.user == @u1}.submission_comments).not_to be_empty
|
|
expect(res.find{|s| s.user == @u2}.submission_comments).not_to be_empty
|
|
# all the comments should have the same group_comment_id, for deletion
|
|
comments = SubmissionComment.for_assignment_id(@a.id).all
|
|
expect(comments.size).to eq 2
|
|
group_comment_id = comments[0].group_comment_id
|
|
expect(group_comment_id).to be_present
|
|
expect(comments.all? { |c| c.group_comment_id == group_comment_id }).to be_truthy
|
|
end
|
|
|
|
it "return the single submission if the user is not in a group" do
|
|
res = @a.grade_student(@u3, :comment => "woot", :group_comment => "1")
|
|
expect(res).not_to be_nil
|
|
expect(res).not_to be_empty
|
|
expect(res.length).to eql(1)
|
|
comments = res.find{|s| s.user == @u3}.submission_comments
|
|
expect(comments.size).to eq 1
|
|
expect(comments[0].group_comment_id).to be_nil
|
|
end
|
|
|
|
it "associates attachments with all submissions" do
|
|
@a.update_attribute :submission_types, "online_upload"
|
|
f = @u1.attachments.create! uploaded_data: StringIO.new('blah'),
|
|
context: @u1,
|
|
filename: 'blah.txt'
|
|
@a.submit_homework(@u1, attachments: [f])
|
|
@a.submissions.reload.each { |s|
|
|
expect(s.attachments).to eq [f]
|
|
}
|
|
end
|
|
end
|
|
|
|
context "adheres_to_policy" do
|
|
it "should serialize permissions" do
|
|
@assignment = @course.assignments.create!(:title => "some assignment")
|
|
data = @assignment.as_json(:permissions => {:user => @user, :session => nil}) rescue nil
|
|
expect(data).not_to be_nil
|
|
expect(data['assignment']).not_to be_nil
|
|
expect(data['assignment']['permissions']).not_to be_nil
|
|
expect(data['assignment']['permissions']).not_to be_empty
|
|
end
|
|
end
|
|
|
|
describe "sections_with_visibility" do
|
|
before(:once) do
|
|
course_with_teacher(:active_all => true)
|
|
@section = @course.course_sections.create!
|
|
@student = student_in_section(@section, opts={})
|
|
@assignment, @assignment2, @assignment3 = (1..3).map{|a|@course.assignments.create!}
|
|
|
|
@assignment.only_visible_to_overrides = true
|
|
create_section_override_for_assignment(@assignment, course_section: @section)
|
|
|
|
@assignment2.only_visible_to_overrides = true
|
|
|
|
@assignment3.only_visible_to_overrides = false
|
|
create_section_override_for_assignment(@assignment3, course_section: @section)
|
|
[@assignment, @assignment2, @assignment3].each(&:save!)
|
|
end
|
|
|
|
it "returns active_course_sections with differentiated assignments off" do
|
|
@course.disable_feature!(:differentiated_assignments)
|
|
expect(@assignment.sections_with_visibility(@teacher)).to eq @course.course_sections
|
|
expect(@assignment2.sections_with_visibility(@teacher)).to eq @course.course_sections
|
|
expect(@assignment3.sections_with_visibility(@teacher)).to eq @course.course_sections
|
|
end
|
|
|
|
it "returns only sections with overrides with differentiated assignments on" do
|
|
@course.enable_feature!(:differentiated_assignments)
|
|
expect(@assignment.sections_with_visibility(@teacher)).to eq [@section]
|
|
expect(@assignment2.sections_with_visibility(@teacher)).to eq []
|
|
expect(@assignment3.sections_with_visibility(@teacher)).to eq @course.course_sections
|
|
end
|
|
end
|
|
|
|
context "modules" do
|
|
it "should be locked when part of a locked module" do
|
|
ag = @course.assignment_groups.create!
|
|
a1 = ag.assignments.create!(:context => course)
|
|
expect(a1.locked_for?(@user)).to be_falsey
|
|
|
|
m = @course.context_modules.create!
|
|
ct = ContentTag.new
|
|
ct.content_id = a1.id
|
|
ct.content_type = 'Assignment'
|
|
ct.context_id = course.id
|
|
ct.context_type = 'Course'
|
|
ct.title = "Assignment"
|
|
ct.tag_type = "context_module"
|
|
ct.context_module_id = m.id
|
|
ct.context_code = "course_#{@course.id}"
|
|
ct.save!
|
|
|
|
m.unlock_at = Time.now.in_time_zone + 1.day
|
|
m.save
|
|
a1.reload
|
|
expect(a1.locked_for?(@user)).to be_truthy
|
|
end
|
|
|
|
it "should be locked when associated discussion topic is part of a locked module" do
|
|
a1 = assignment_model(:course => @course, :submission_types => "discussion_topic")
|
|
a1.reload
|
|
expect(a1.locked_for?(@user)).to be_falsey
|
|
|
|
m = @course.context_modules.create!
|
|
m.add_item(:id => a1.discussion_topic.id, :type => 'discussion_topic')
|
|
|
|
m.unlock_at = Time.now.in_time_zone + 1.day
|
|
m.save
|
|
a1.reload
|
|
expect(a1.locked_for?(@user)).to be_truthy
|
|
end
|
|
|
|
it "should be locked when associated quiz is part of a locked module" do
|
|
a1 = assignment_model(:course => @course, :submission_types => "online_quiz")
|
|
a1.reload
|
|
expect(a1.locked_for?(@user)).to be_falsey
|
|
|
|
m = @course.context_modules.create!
|
|
m.add_item(:id => a1.quiz.id, :type => 'quiz')
|
|
|
|
m.unlock_at = Time.now.in_time_zone + 1.day
|
|
m.save
|
|
a1.reload
|
|
expect(a1.locked_for?(@user)).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "group_students" do
|
|
it "should return [nil, [student]] unless the assignment has a group_category" do
|
|
@assignment = assignment_model(course: @course)
|
|
@student = user_model
|
|
expect(@assignment.group_students(@student)).to eq [nil, [@student]]
|
|
end
|
|
|
|
it "should return [nil, [student]] if the context doesn't have any active groups in the same category" do
|
|
@assignment = assignment_model(:group_category => "Fake Category", :course => @course)
|
|
@student = user_model
|
|
expect(@assignment.group_students(@student)).to eq [nil, [@student]]
|
|
end
|
|
|
|
it "should return [nil, [student]] if the student isn't in any of the candidate groups" do
|
|
@assignment = assignment_model(:group_category => "Category", :course => @course)
|
|
@group = @course.groups.create(:name => "Group", :group_category => @assignment.group_category)
|
|
@student = user_model
|
|
expect(@assignment.group_students(@student)).to eq [nil, [@student]]
|
|
end
|
|
|
|
it "should return [group, [students from group]] if the student is in one of the candidate groups" do
|
|
@assignment = assignment_model(:group_category => "Category", :course => @course)
|
|
@course.enroll_student(@student1 = user_model)
|
|
@course.enroll_student(@student2 = user_model)
|
|
@course.enroll_student(@student3 = user_model)
|
|
@group1 = @course.groups.create(:name => "Group 1", :group_category => @assignment.group_category)
|
|
@group1.add_user(@student1)
|
|
@group1.add_user(@student2)
|
|
@group2 = @course.groups.create(:name => "Group 2", :group_category => @assignment.group_category)
|
|
@group2.add_user(@student3)
|
|
|
|
# have to reload because the enrolled students above don't show up in
|
|
# Course#students until the course has been reloaded
|
|
result = @assignment.reload.group_students(@student1)
|
|
expect(result.first).to eq @group1
|
|
expect(result.last.map{ |u| u.id }.sort).to eq [@student1, @student2].map{ |u| u.id }.sort
|
|
end
|
|
|
|
it "returns distinct users" do
|
|
s1, s2 = n_students_in_course(2)
|
|
|
|
section = @course.course_sections.create! name: "some section"
|
|
e = @course.enroll_user s1, 'StudentEnrollment',
|
|
section: section,
|
|
allow_multiple_enrollments: true
|
|
e.update_attribute :workflow_state, 'active'
|
|
|
|
gc = @course.group_categories.create! name: "Homework Groups"
|
|
group = gc.groups.create! name: "Group 1", context: @course
|
|
group.add_user(s1)
|
|
group.add_user(s2)
|
|
|
|
a = @course.assignments.create! name: "Group Assignment",
|
|
group_category_id: gc.id
|
|
g, students = a.group_students(s1)
|
|
expect(g).to eq group
|
|
expect(students.sort_by(&:id)).to eq [s1, s2]
|
|
end
|
|
end
|
|
|
|
it "should maintain the deprecated group_category attribute" do
|
|
assignment = assignment_model(course: @course)
|
|
expect(assignment.read_attribute(:group_category)).to be_nil
|
|
assignment.group_category = assignment.context.group_categories.create(:name => "my category")
|
|
assignment.save
|
|
assignment.reload
|
|
expect(assignment.read_attribute(:group_category)).to eql("my category")
|
|
assignment.group_category = nil
|
|
assignment.save
|
|
assignment.reload
|
|
expect(assignment.read_attribute(:group_category)).to be_nil
|
|
end
|
|
|
|
it "should provide has_group_category?" do
|
|
assignment = assignment_model(course: @course)
|
|
expect(assignment.has_group_category?).to be_falsey
|
|
assignment.group_category = assignment.context.group_categories.create(:name => "my category")
|
|
expect(assignment.has_group_category?).to be_truthy
|
|
assignment.group_category = nil
|
|
expect(assignment.has_group_category?).to be_falsey
|
|
end
|
|
|
|
context "turnitin settings" do
|
|
before(:once) { assignment_model(course: @course) }
|
|
|
|
it "should sanitize bad data" do
|
|
assignment = @assignment
|
|
assignment.turnitin_settings = {
|
|
:originality_report_visibility => 'invalid',
|
|
:s_paper_check => '2',
|
|
:internet_check => 1,
|
|
:journal_check => 0,
|
|
:exclude_biblio => true,
|
|
:exclude_quoted => false,
|
|
:exclude_type => '3',
|
|
:exclude_value => 'asdf',
|
|
:bogus => 'haha'
|
|
}
|
|
expect(assignment.turnitin_settings).to eql({
|
|
:originality_report_visibility => 'immediate',
|
|
:s_paper_check => '1',
|
|
:internet_check => '1',
|
|
:journal_check => '0',
|
|
:exclude_biblio => '1',
|
|
:exclude_quoted => '0',
|
|
:exclude_type => '0',
|
|
:exclude_value => '',
|
|
:s_view_report => '1',
|
|
:submit_papers_to => '0'
|
|
})
|
|
end
|
|
|
|
it "should persist :created across changes" do
|
|
assignment = @assignment
|
|
assignment.turnitin_settings = Turnitin::Client.default_assignment_turnitin_settings
|
|
assignment.save
|
|
assignment.turnitin_settings[:created] = true
|
|
assignment.save
|
|
assignment.reload
|
|
expect(assignment.turnitin_settings[:created]).to be_truthy
|
|
|
|
assignment.turnitin_settings = Turnitin::Client.default_assignment_turnitin_settings.merge(:s_paper_check => '0')
|
|
assignment.save
|
|
assignment.reload
|
|
expect(assignment.turnitin_settings[:created]).to be_truthy
|
|
end
|
|
|
|
it "should clear out :current" do
|
|
assignment = @assignment
|
|
assignment.turnitin_settings = Turnitin::Client.default_assignment_turnitin_settings
|
|
assignment.save
|
|
assignment.turnitin_settings[:current] = true
|
|
assignment.save
|
|
assignment.reload
|
|
expect(assignment.turnitin_settings[:current]).to be_truthy
|
|
|
|
assignment.turnitin_settings = Turnitin::Client.default_assignment_turnitin_settings.merge(:s_paper_check => '0')
|
|
assignment.save
|
|
assignment.reload
|
|
expect(assignment.turnitin_settings[:current]).to be_nil
|
|
end
|
|
end
|
|
|
|
context "generate comments from submissions" do
|
|
def create_and_submit
|
|
setup_assignment_without_submission
|
|
|
|
@attachment = @user.attachments.new :filename => "homework.doc"
|
|
@attachment.content_type = "foo/bar"
|
|
@attachment.size = 10
|
|
@attachment.save!
|
|
|
|
@submission = @assignment.submit_homework @user, :submission_type => :online_upload, :attachments => [@attachment]
|
|
end
|
|
|
|
it "should infer_comment_context_from_filename" do
|
|
create_and_submit
|
|
ignore_file = "/tmp/._why_macos_why.txt"
|
|
@assignment.instance_variable_set :@ignored_files, []
|
|
expect(@assignment.send(:infer_comment_context_from_filename, ignore_file)).to be_nil
|
|
expect(@assignment.instance_variable_get(:@ignored_files)).to eq [ignore_file]
|
|
|
|
filename = [@user.last_name_first, @user.id, @attachment.id, @attachment.display_name].join("_")
|
|
|
|
expect(@assignment.send(:infer_comment_context_from_filename, filename)).to eq({
|
|
:user => @user,
|
|
:submission => @submission,
|
|
:filename => filename,
|
|
:display_name => @attachment.display_name
|
|
})
|
|
expect(@assignment.instance_variable_get(:@ignored_files)).to eq [ignore_file]
|
|
end
|
|
|
|
it "should mark comments as hidden for submission zip uploads" do
|
|
@assignment = @course.assignments.create! name: "Mute Comment Test",
|
|
submission_types: %w(online_upload)
|
|
@assignment.update_attribute :muted, true
|
|
submit_homework(@student)
|
|
|
|
zip = zip_submissions
|
|
|
|
@assignment.generate_comments_from_files(zip.open.path, @user)
|
|
|
|
submission = @assignment.submission_for_student(@student)
|
|
expect(submission.submission_comments.last.hidden).to eq true
|
|
end
|
|
end
|
|
|
|
context "attribute freezing" do
|
|
before :once do
|
|
@asmnt = @course.assignments.create!(:title => 'lock locky')
|
|
@att_map = {"lock_at" => "yes",
|
|
"assignment_group" => "no",
|
|
"title" => "no",
|
|
"assignment_group_id" => "no",
|
|
"submission_types" => "yes",
|
|
"points_possible" => "yes",
|
|
"description" => "yes",
|
|
"grading_type" => "yes"}
|
|
end
|
|
|
|
def stub_plugin
|
|
PluginSetting.stubs(:settings_for_plugin).returns(@att_map)
|
|
end
|
|
|
|
it "should not be frozen if not copied" do
|
|
stub_plugin
|
|
@asmnt.freeze_on_copy = true
|
|
expect(@asmnt.frozen?).to eq false
|
|
@att_map.each_key{|att| expect(@asmnt.att_frozen?(att)).to eq false}
|
|
end
|
|
|
|
it "should not be frozen if copied but not frozen set" do
|
|
stub_plugin
|
|
@asmnt.copied = true
|
|
expect(@asmnt.frozen?).to eq false
|
|
@att_map.each_key{|att| expect(@asmnt.att_frozen?(att)).to eq false}
|
|
end
|
|
|
|
it "should not be frozen if plugin not enabled" do
|
|
@asmnt.copied = true
|
|
@asmnt.freeze_on_copy = true
|
|
expect(@asmnt.frozen?).to eq false
|
|
@att_map.each_key{|att| expect(@asmnt.att_frozen?(att)).to eq false}
|
|
end
|
|
|
|
context "assignments are frozen" do
|
|
before :once do
|
|
@admin = account_admin_user(opts={})
|
|
teacher_in_course(:course => @course)
|
|
end
|
|
|
|
before :each do
|
|
stub_plugin
|
|
@asmnt.copied = true
|
|
@asmnt.freeze_on_copy = true
|
|
end
|
|
|
|
it "should be frozen" do
|
|
expect(@asmnt.frozen?).to eq true
|
|
end
|
|
|
|
it "should flag specific attributes as frozen for no user" do
|
|
@att_map.each_pair do |att, setting|
|
|
expect(@asmnt.att_frozen?(att)).to eq(setting == "yes")
|
|
end
|
|
end
|
|
|
|
it "should flag specific attributes as frozen for teacher" do
|
|
@att_map.each_pair do |att, setting|
|
|
expect(@asmnt.att_frozen?(att, @teacher)).to eq(setting == "yes")
|
|
end
|
|
end
|
|
|
|
it "should not flag attributes as frozen for admin" do
|
|
@att_map.each_pair do |att, setting|
|
|
expect(@asmnt.att_frozen?(att, @admin)).to eq false
|
|
end
|
|
end
|
|
|
|
it "should be frozen for nil user" do
|
|
expect(@asmnt.frozen_for_user?(nil)).to eq true
|
|
end
|
|
|
|
it "should not be frozen for admin" do
|
|
expect(@asmnt.frozen_for_user?(@admin)).to eq false
|
|
end
|
|
|
|
it "should not validate if saving without user" do
|
|
@asmnt.description = "new description"
|
|
@asmnt.save
|
|
expect(@asmnt.valid?).to eq false
|
|
expect(@asmnt.errors["description"]).to eq ["You don't have permission to edit the locked attribute description"]
|
|
end
|
|
|
|
it "should allow teacher to edit unlocked attributes" do
|
|
@asmnt.title = "new title"
|
|
@asmnt.updating_user = @teacher
|
|
@asmnt.save!
|
|
|
|
@asmnt.reload
|
|
expect(@asmnt.title).to eq "new title"
|
|
end
|
|
|
|
it "should not allow teacher to edit locked attributes" do
|
|
@asmnt.description = "new description"
|
|
@asmnt.updating_user = @teacher
|
|
@asmnt.save
|
|
|
|
expect(@asmnt.valid?).to eq false
|
|
expect(@asmnt.errors["description"]).to eq ["You don't have permission to edit the locked attribute description"]
|
|
|
|
@asmnt.reload
|
|
expect(@asmnt.description).not_to eq "new title"
|
|
end
|
|
|
|
it "should allow admin to edit unlocked attributes" do
|
|
@asmnt.description = "new description"
|
|
@asmnt.updating_user = @admin
|
|
@asmnt.save!
|
|
|
|
@asmnt.reload
|
|
expect(@asmnt.description).to eq "new description"
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
context "not_locked scope" do
|
|
before :once do
|
|
assignment_quiz([], :course => @course, :user => @user)
|
|
# Setup default values for tests (leave unsaved for easy changes)
|
|
@quiz.unlock_at = nil
|
|
@quiz.lock_at = nil
|
|
@quiz.due_at = 2.days.from_now
|
|
end
|
|
|
|
before :each do
|
|
user_session(@user)
|
|
end
|
|
|
|
it "should include assignments with no locks" do
|
|
@quiz.save!
|
|
list = Assignment.not_locked.all
|
|
expect(list.size).to eql 1
|
|
expect(list.first.title).to eql 'Test Assignment'
|
|
end
|
|
it "should include assignments with unlock_at in the past" do
|
|
@quiz.unlock_at = 1.day.ago
|
|
@quiz.save!
|
|
list = Assignment.not_locked.all
|
|
expect(list.size).to eql 1
|
|
expect(list.first.title).to eql 'Test Assignment'
|
|
end
|
|
it "should include assignments where lock_at is future" do
|
|
@quiz.lock_at = 1.day.from_now
|
|
@quiz.save!
|
|
list = Assignment.not_locked.all
|
|
expect(list.size).to eql 1
|
|
expect(list.first.title).to eql 'Test Assignment'
|
|
end
|
|
it "should include assignments where unlock_at is in the past and lock_at is future" do
|
|
@quiz.unlock_at = 1.day.ago
|
|
@quiz.lock_at = 1.day.from_now
|
|
@quiz.save!
|
|
list = Assignment.not_locked.all
|
|
expect(list.size).to eql 1
|
|
expect(list.first.title).to eql 'Test Assignment'
|
|
end
|
|
it "should not include assignments where unlock_at is in future" do
|
|
@quiz.unlock_at = 1.hour.from_now
|
|
@quiz.save!
|
|
expect(Assignment.not_locked.count).to eq 0
|
|
end
|
|
it "should not include assignments where lock_at is in past" do
|
|
@quiz.lock_at = 1.hours.ago
|
|
@quiz.save!
|
|
expect(Assignment.not_locked.count).to eq 0
|
|
end
|
|
end
|
|
|
|
context "due_between_with_overrides" do
|
|
before :once do
|
|
@assignment = @course.assignments.create!(:title => 'assignment', :due_at => Time.now)
|
|
@overridden_assignment = @course.assignments.create!(:title => 'overridden_assignment', :due_at => Time.now)
|
|
|
|
override = @assignment.assignment_overrides.build
|
|
override.due_at = Time.now
|
|
override.title = 'override'
|
|
override.save!
|
|
end
|
|
|
|
before :each do
|
|
@results = @course.assignments.due_between_with_overrides(Time.now - 1.day, Time.now + 1.day)
|
|
end
|
|
|
|
it 'should return assignments between the given dates' do
|
|
expect(@results).to include(@assignment)
|
|
end
|
|
|
|
it 'should return overridden assignments that are due between the given dates' do
|
|
expect(@results).to include(@overridden_assignment)
|
|
end
|
|
end
|
|
|
|
context "destroy" do
|
|
before :once do
|
|
group_discussion_assignment
|
|
end
|
|
|
|
it "should destroy the associated discussion topic" do
|
|
@assignment.destroy
|
|
expect(@topic.reload).to be_deleted
|
|
expect(@assignment.reload).to be_deleted
|
|
end
|
|
|
|
it "should not revive the discussion if touched after destroyed" do
|
|
@assignment.destroy
|
|
expect(@topic.reload).to be_deleted
|
|
@assignment.touch
|
|
expect(@topic.reload).to be_deleted
|
|
end
|
|
it 'should raise an error on validation error' do
|
|
assignment = Assignment.new
|
|
expect {assignment.destroy}.to raise_error(ActiveRecord::RecordInvalid)
|
|
end
|
|
end
|
|
|
|
describe "speed_grader_json" do
|
|
it "should include comments' created_at" do
|
|
setup_assignment_with_homework
|
|
@submission = @assignment.submissions.first
|
|
@comment = @submission.add_comment(:comment => 'comment')
|
|
json = @assignment.speed_grader_json(@user)
|
|
expect(json[:submissions].first[:submission_comments].first[:created_at].to_i).to eql @comment.created_at.to_i
|
|
end
|
|
|
|
context "students and active course sections" do
|
|
before(:once) do
|
|
@course = course(:active_course => true)
|
|
@teacher, @student1, @student2 = (1..3).map{User.create}
|
|
@assignment = Assignment.create!(title: "title", context: @course, only_visible_to_overrides: true)
|
|
@course.enroll_teacher(@teacher)
|
|
@course.enroll_student(@student2, :enrollment_state => 'active')
|
|
@section1 = @course.course_sections.create!(name: "test section 1")
|
|
@section2 = @course.course_sections.create!(name: "test section 2")
|
|
student_in_section(@section1, user: @student1)
|
|
create_section_override_for_assignment(@assignment, {course_section: @section1})
|
|
end
|
|
|
|
it "should include all students and sections with DA off" do
|
|
@course.disable_feature!(:differentiated_assignments)
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
|
|
expect(json[:context][:students].map{|s| s[:id]}.include?(@student1.id)).to be_truthy
|
|
expect(json[:context][:students].map{|s| s[:id]}.include?(@student2.id)).to be_truthy
|
|
expect(json[:context][:active_course_sections].map{|cs| cs[:id]}.include?(@section1.id)).to be_truthy
|
|
expect(json[:context][:active_course_sections].map{|cs| cs[:id]}.include?(@section2.id)).to be_truthy
|
|
end
|
|
|
|
it "should include only students and sections with overrides when DA is on" do
|
|
@course.enable_feature!(:differentiated_assignments)
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
|
|
expect(json[:context][:students].map{|s| s[:id]}.include?(@student1.id)).to be_truthy
|
|
expect(json[:context][:students].map{|s| s[:id]}.include?(@student2.id)).to be_falsey
|
|
expect(json[:context][:active_course_sections].map{|cs| cs[:id]}.include?(@section1.id)).to be_truthy
|
|
expect(json[:context][:active_course_sections].map{|cs| cs[:id]}.include?(@section2.id)).to be_falsey
|
|
end
|
|
|
|
it "should include all students when is only_visible_to_overrides false" do
|
|
@course.enable_feature!(:differentiated_assignments)
|
|
@assignment.only_visible_to_overrides = false
|
|
@assignment.save!
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
|
|
expect(json[:context][:students].map{|s| s[:id]}.include?(@student1.id)).to be_truthy
|
|
expect(json[:context][:students].map{|s| s[:id]}.include?(@student2.id)).to be_truthy
|
|
expect(json[:context][:active_course_sections].map{|cs| cs[:id]}.include?(@section1.id)).to be_truthy
|
|
expect(json[:context][:active_course_sections].map{|cs| cs[:id]}.include?(@section2.id)).to be_truthy
|
|
end
|
|
end
|
|
|
|
it "should return submission lateness" do
|
|
# Set up
|
|
section_1 = @course.course_sections.create!(:name => 'Section one')
|
|
section_2 = @course.course_sections.create!(:name => 'Section two')
|
|
|
|
assignment = @course.assignments.create!(:title => 'Overridden assignment', :due_at => Time.now - 5.days)
|
|
|
|
student_1 = user_with_pseudonym(:active_all => true, :username => 'student1@example.com')
|
|
student_2 = user_with_pseudonym(:active_all => true, :username => 'student2@example.com')
|
|
|
|
@course.enroll_student(student_1, :section => section_1).accept!
|
|
@course.enroll_student(student_2, :section => section_2).accept!
|
|
|
|
o1 = assignment.assignment_overrides.build
|
|
o1.due_at = Time.now - 2.days
|
|
o1.due_at_overridden = true
|
|
o1.set = section_1
|
|
o1.save!
|
|
|
|
o2 = assignment.assignment_overrides.build
|
|
o2.due_at = Time.now + 2.days
|
|
o2.due_at_overridden = true
|
|
o2.set = section_2
|
|
o2.save!
|
|
|
|
submission_1 = assignment.submit_homework(student_1, :submission_type => 'online_text_entry', :body => 'blah')
|
|
submission_2 = assignment.submit_homework(student_2, :submission_type => 'online_text_entry', :body => 'blah')
|
|
|
|
# Test
|
|
json = assignment.speed_grader_json(@teacher)
|
|
json[:submissions].each do |submission|
|
|
user = [student_1, student_2].detect { |s| s.id == submission[:user_id] }
|
|
expect(submission[:late]).to eq user.submissions.first.late?
|
|
end
|
|
end
|
|
|
|
it "should include inline view pingback url for files" do
|
|
assignment = @course.assignments.create! :submission_types => ['online_upload']
|
|
attachment = @student.attachments.create! :uploaded_data => dummy_io, :filename => 'doc.doc', :display_name => 'doc.doc', :context => @student
|
|
submission = assignment.submit_homework @student, :submission_type => :online_upload, :attachments => [attachment]
|
|
json = assignment.speed_grader_json @teacher
|
|
attachment_json = json['submissions'][0]['submission_history'][0]['submission']['versioned_attachments'][0]['attachment']
|
|
expect(attachment_json['view_inline_ping_url']).to match %r{/users/#{@student.id}/files/#{attachment.id}/inline_view\z}
|
|
end
|
|
|
|
context "group assignments" do
|
|
before :once do
|
|
course_with_teacher(active_all: true)
|
|
gc = @course.group_categories.create! name: "Assignment Groups"
|
|
@groups = 2.times.map { |i| gc.groups.create! name: "Group #{i}", context: @course }
|
|
students = create_users_in_course(@course, 4, return_type: :record)
|
|
students.each_with_index { |s, i| @groups[i % @groups.size].add_user(s) }
|
|
@assignment = @course.assignments.create!(
|
|
group_category_id: gc.id,
|
|
grade_group_students_individually: false,
|
|
submission_types: %w(text_entry)
|
|
)
|
|
end
|
|
|
|
it "should not be in group mode for non-group assignments" do
|
|
setup_assignment_with_homework
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
expect(json["GROUP_GRADING_MODE"]).not_to be_truthy
|
|
end
|
|
|
|
it 'returns "groups" instead of students' do
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
@groups.each do |group|
|
|
j = json["context"]["students"].find { |g| g["name"] == group.name }
|
|
expect(group.users.map(&:id)).to include j["id"]
|
|
end
|
|
expect(json["GROUP_GRADING_MODE"]).to be_truthy
|
|
end
|
|
|
|
it 'chooses the student with turnitin data to represent' do
|
|
turnitin_submissions = @groups.map do |group|
|
|
rep = group.users.shuffle.first
|
|
turnitin_submission, *others = @assignment.grade_student(rep, grade: 10)
|
|
turnitin_submission.update_attribute :turnitin_data, {blah: 1}
|
|
turnitin_submission
|
|
end
|
|
|
|
@assignment.update_attribute :turnitin_enabled, true
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
|
|
expect(json["submissions"].map { |s|
|
|
s["id"]
|
|
}.sort).to eq turnitin_submissions.map(&:id).sort
|
|
end
|
|
|
|
it 'prefers people with submissions' do
|
|
g1, _ = @groups
|
|
@assignment.grade_student(g1.users.first, score: 10)
|
|
g1rep = g1.users.shuffle.first
|
|
s = @assignment.submission_for_student(g1rep)
|
|
s.update_attribute :submission_type, 'online_upload'
|
|
expect(@assignment.representatives(@teacher)).to include g1rep
|
|
end
|
|
|
|
it "includes users who aren't in a group" do
|
|
student_in_course active_all: true
|
|
expect(@assignment.representatives(@teacher).last).to eq @student
|
|
end
|
|
end
|
|
|
|
context "quizzes" do
|
|
it "works for quizzes without quiz_submissions" do
|
|
quiz = @course.quizzes.create! :title => "Final",
|
|
:quiz_type => "assignment"
|
|
quiz.did_edit
|
|
quiz.offer
|
|
|
|
assignment = quiz.assignment
|
|
assignment.grade_student(@student, grade: 1)
|
|
json = assignment.speed_grader_json(@teacher)
|
|
expect(json[:submissions].all? { |s|
|
|
s.has_key? 'submission_history'
|
|
}).to be_truthy
|
|
end
|
|
|
|
context "with quiz_submissions" do
|
|
before :once do
|
|
quiz_with_graded_submission [], :course => @course, :user => @student
|
|
end
|
|
|
|
it "doesn't include quiz_submissions when there are too many attempts" do
|
|
Setting.set('too_many_quiz_submission_versions', 3)
|
|
3.times {
|
|
@quiz_submission.versions.create!
|
|
}
|
|
json = @quiz.assignment.speed_grader_json(@teacher)
|
|
json[:submissions].all? { |s| expect(s["submission_history"].size).to eq 1 }
|
|
end
|
|
|
|
it "returns quiz lateness correctly" do
|
|
@quiz.time_limit = 10
|
|
@quiz.save!
|
|
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
expect(json[:submissions].first['submission_history'].first[:submission]['late']).to be_falsey
|
|
|
|
@quiz.due_at = 1.day.ago
|
|
@quiz.save!
|
|
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
expect(json[:submissions].first['submission_history'].first[:submission]['late']).to be_truthy
|
|
end
|
|
|
|
it "returns quiz history for records before and after namespace change" do
|
|
@quiz.save!
|
|
|
|
json = @assignment.speed_grader_json(@teacher)
|
|
expect(json[:submissions].first['submission_history'].size).to eq 1
|
|
|
|
Version.update_all("versionable_type = 'QuizSubmission'", "versionable_type = 'Quizzes::QuizSubmission'")
|
|
json = @assignment.reload.speed_grader_json(@teacher)
|
|
expect(json[:submissions].first['submission_history'].size).to eq 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#too_many_qs_versions" do
|
|
it "returns if there are too many versions to load at once" do
|
|
quiz_with_graded_submission [], :course => @course, :user => @student
|
|
submissions = @quiz.assignment.submissions
|
|
|
|
Setting.set('too_many_quiz_submission_versions', 3)
|
|
1.times { @quiz_submission.versions.create! }
|
|
expect(@quiz.assignment.too_many_qs_versions?(submissions)).to be_falsey
|
|
|
|
2.times { @quiz_submission.versions.create! }
|
|
expect(@quiz.reload.assignment.too_many_qs_versions?(submissions)).to be_truthy
|
|
end
|
|
end
|
|
|
|
describe "#quiz_submission_versions" do
|
|
it "finds quiz submission versions for submissions" do
|
|
quiz_with_graded_submission([], { :course => @course, :user => @student })
|
|
@quiz.save!
|
|
|
|
assignment = @quiz.assignment
|
|
submissions = assignment.submissions
|
|
too_many = assignment.too_many_qs_versions?(submissions)
|
|
|
|
versions = assignment.quiz_submission_versions(submissions, too_many)
|
|
|
|
expect(versions[@quiz_submission.id].size).to eq 1
|
|
end
|
|
end
|
|
|
|
describe "update_student_submissions" do
|
|
before :once do
|
|
@student1, @student2 = create_users_in_course(@course, 2, return_type: :record)
|
|
@assignment = @course.assignments.create! grading_type: "pass_fail",
|
|
points_possible: 5
|
|
@sub1 = @assignment.grade_student(@student1, grade: "complete").first
|
|
@sub2 = @assignment.grade_student(@student2, grade: "incomplete").first
|
|
end
|
|
|
|
it "should save a version when changing grades" do
|
|
@assignment.update_attribute :points_possible, 10
|
|
expect(@sub1.reload.version_number).to eq 2
|
|
end
|
|
|
|
it "works for pass/fail assignments" do
|
|
@assignment.update_attribute :points_possible, 10
|
|
expect(@sub1.reload.grade).to eq "complete"
|
|
expect(@sub2.reload.grade).to eq "incomplete"
|
|
end
|
|
|
|
it "works for pass/fail assignments with 0 points possible" do
|
|
@assignment.update_attribute :points_possible, 0
|
|
expect(@sub1.reload.grade).to eq "complete"
|
|
expect(@sub2.reload.grade).to eq "incomplete"
|
|
end
|
|
end
|
|
|
|
describe '#graded_count' do
|
|
before :once do
|
|
setup_assignment_without_submission
|
|
@assignment.grade_student(@user, :grade => 1)
|
|
end
|
|
|
|
it 'counts the submissions that have been graded' do
|
|
expect(@assignment.graded_count).to eq 1
|
|
end
|
|
|
|
it 'returns the cached value if present' do
|
|
@assignment = Assignment.select("assignments.*, 50 AS graded_count").where(id: @assignment).first
|
|
expect(@assignment.graded_count).to eq 50
|
|
end
|
|
end
|
|
|
|
describe '#submitted_count' do
|
|
before :once do
|
|
setup_assignment_without_submission
|
|
@assignment.grade_student(@user, :grade => 1)
|
|
@assignment.submissions.first.update_attribute(:submission_type, 'online_url')
|
|
end
|
|
|
|
it 'counts the submissions that have submission types' do
|
|
expect(@assignment.submitted_count).to eq 1
|
|
end
|
|
|
|
it 'returns the cached value if present' do
|
|
@assignment = Assignment.select("assignments.*, 50 AS submitted_count").where(id: @assignment).first
|
|
expect(@assignment.submitted_count).to eq 50
|
|
end
|
|
end
|
|
|
|
describe "linking overrides with quizzes" do
|
|
let_once(:assignment) { assignment_model(:course => @course, :due_at => 5.days.from_now).reload }
|
|
let_once(:override) { assignment_override_model(:assignment => assignment) }
|
|
|
|
before :once do
|
|
override.override_due_at(7.days.from_now)
|
|
override.save!
|
|
|
|
@override_student = override.assignment_override_students.build
|
|
@override_student.user = @student
|
|
@override_student.save!
|
|
end
|
|
|
|
context "before the assignment has a quiz" do
|
|
context "override" do
|
|
it "has a nil quiz" do
|
|
expect(override.quiz).to be_nil
|
|
end
|
|
|
|
it "has an assignment" do
|
|
expect(override.assignment).to eq assignment
|
|
end
|
|
end
|
|
|
|
context "override student" do
|
|
it "has a nil quiz" do
|
|
expect(@override_student.quiz).to be_nil
|
|
end
|
|
|
|
it "has an assignment" do
|
|
expect(@override_student.assignment).to eq assignment
|
|
end
|
|
end
|
|
end
|
|
|
|
context "once the assignment changes to a quiz submission" do
|
|
before :once do
|
|
assignment.submission_types = "online_quiz"
|
|
assignment.save
|
|
assignment.reload
|
|
override.reload
|
|
@override_student.reload
|
|
end
|
|
|
|
it "has a quiz" do
|
|
expect(assignment.quiz).to be_present
|
|
end
|
|
|
|
context "override" do
|
|
it "has an assignment" do
|
|
expect(override.assignment).to eq assignment
|
|
end
|
|
|
|
it "has the assignment's quiz" do
|
|
expect(override.quiz).to eq assignment.quiz
|
|
end
|
|
end
|
|
|
|
context "override student" do
|
|
it "has an assignment" do
|
|
expect(@override_student.assignment).to eq assignment
|
|
end
|
|
|
|
it "has the assignment's quiz" do
|
|
expect(@override_student.quiz).to eq assignment.quiz
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "updating cached due dates" do
|
|
before :once do
|
|
@assignment = assignment_model(course: @course)
|
|
@assignment.due_at = 2.weeks.from_now
|
|
@assignment.save
|
|
end
|
|
|
|
it "triggers when assignment is created" do
|
|
new_assignment = @course.assignments.build
|
|
DueDateCacher.expects(:recompute).with(new_assignment)
|
|
new_assignment.save
|
|
end
|
|
|
|
it "triggers when due_at changes" do
|
|
DueDateCacher.expects(:recompute).with(@assignment)
|
|
@assignment.due_at = 1.week.from_now
|
|
@assignment.save
|
|
end
|
|
|
|
it "triggers when due_at changes to nil" do
|
|
DueDateCacher.expects(:recompute).with(@assignment)
|
|
@assignment.due_at = nil
|
|
@assignment.save
|
|
end
|
|
|
|
it "triggers when assignment deleted" do
|
|
DueDateCacher.expects(:recompute).with(@assignment)
|
|
@assignment.destroy
|
|
end
|
|
|
|
it "does not trigger when nothing changed" do
|
|
DueDateCacher.expects(:recompute).never
|
|
@assignment.save
|
|
end
|
|
end
|
|
|
|
describe "#title_slug" do
|
|
before :once do
|
|
@assignment = assignment_model(course: @course)
|
|
end
|
|
|
|
it "should hard truncate at 30 characters" do
|
|
@assignment.title = "a" * 31
|
|
expect(@assignment.title.length).to eq 31
|
|
expect(@assignment.title_slug.length).to eq 30
|
|
expect(@assignment.title).to match /^#{@assignment.title_slug}/
|
|
end
|
|
|
|
it "should not change the title" do
|
|
title = "a" * 31
|
|
@assignment.title = title
|
|
expect(@assignment.title_slug).not_to eq @assignment.title
|
|
expect(@assignment.title).to eq title
|
|
end
|
|
|
|
it "should leave short titles alone" do
|
|
@assignment.title = 'short title'
|
|
expect(@assignment.title_slug).to eq @assignment.title
|
|
end
|
|
|
|
it "should not allow titles over 255 char" do
|
|
@assignment.title = 'qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
|
|
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
|
|
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
|
|
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm
|
|
qwertyuiopasdfghjklzxcvbnmqwertyuiopasdfghjklzxcvbnm'
|
|
|
|
expect(lambda { @assignment.save! }).to raise_error("Validation failed: Title is too long (maximum is 255 characters), Title is too long (maximum is 255 characters)")
|
|
end
|
|
end
|
|
|
|
describe "external_tool_tag" do
|
|
it "should update the existing tag when updating the assignment" do
|
|
a = @course.assignments.create!(title: "test",
|
|
submission_types: 'external_tool',
|
|
external_tool_tag_attributes: {url: "http://example.com/launch"})
|
|
tag = a.external_tool_tag
|
|
expect(tag).not_to be_new_record
|
|
|
|
a = Assignment.find(a.id)
|
|
a.attributes = {external_tool_tag_attributes: {url: "http://example.com/launch2"}}
|
|
a.save!
|
|
expect(a.external_tool_tag.url).to eq "http://example.com/launch2"
|
|
expect(a.external_tool_tag).to eq tag
|
|
end
|
|
end
|
|
|
|
describe "allowed_extensions=" do
|
|
it "should accept a string as input" do
|
|
a = Assignment.new
|
|
a.allowed_extensions = "doc,xls,txt"
|
|
expect(a.allowed_extensions).to eq ["doc", "xls", "txt"]
|
|
end
|
|
|
|
it "should accept an array as input" do
|
|
a = Assignment.new
|
|
a.allowed_extensions = ["doc", "xls", "txt"]
|
|
expect(a.allowed_extensions).to eq ["doc", "xls", "txt"]
|
|
end
|
|
|
|
it "should sanitize the string" do
|
|
a = Assignment.new
|
|
a.allowed_extensions = ".DOC, .XLS, .TXT"
|
|
expect(a.allowed_extensions).to eq ["doc", "xls", "txt"]
|
|
end
|
|
|
|
it "should sanitize the array" do
|
|
a = Assignment.new
|
|
a.allowed_extensions = [".DOC", " .XLS", " .TXT"]
|
|
expect(a.allowed_extensions).to eq ["doc", "xls", "txt"]
|
|
end
|
|
end
|
|
|
|
describe '#generate_comments_from_files' do
|
|
before :once do
|
|
@students = create_users_in_course(@course, 3, return_type: :record)
|
|
|
|
@assignment = @course.assignments.create! :name => "zip upload test",
|
|
:submission_types => %w(online_upload)
|
|
end
|
|
|
|
it "should work for individuals" do
|
|
s1 = @students.first
|
|
submit_homework(s1)
|
|
|
|
zip = zip_submissions
|
|
|
|
comments, ignored = @assignment.generate_comments_from_files(
|
|
zip.open.path,
|
|
@teacher)
|
|
|
|
expect(comments.map { |g| g.map { |c| c.submission.user } }).to eq [[s1]]
|
|
expect(ignored).to be_empty
|
|
end
|
|
|
|
it "should work for groups" do
|
|
s1, s2 = @students
|
|
|
|
gc = @course.group_categories.create! name: "Homework Groups"
|
|
@assignment.update_attributes group_category_id: gc.id,
|
|
grade_group_students_individually: false
|
|
g1, g2 = 2.times.map { |i| gc.groups.create! name: "Group #{i}", context: @course }
|
|
g1.add_user(s1)
|
|
g1.add_user(s2)
|
|
|
|
submit_homework(s1)
|
|
zip = zip_submissions
|
|
|
|
comments, _ = @assignment.generate_comments_from_files(
|
|
zip.open.path,
|
|
@teacher)
|
|
|
|
expect(comments.map { |g|
|
|
g.map { |c| c.submission.user }.sort_by(&:id)
|
|
}).to eq [[s1, s2]]
|
|
end
|
|
end
|
|
|
|
describe "#restore" do
|
|
it "should restore to unpublished if draft state w/ no submissions" do
|
|
assignment_model course: @course
|
|
@a.destroy
|
|
@a.restore
|
|
expect(@a.reload).to be_unpublished
|
|
end
|
|
|
|
it "should restore to published if draft state w/ submissions" do
|
|
setup_assignment_with_homework
|
|
@assignment.destroy
|
|
@assignment.restore
|
|
expect(@assignment.reload).to be_published
|
|
end
|
|
end
|
|
|
|
describe '#readable_submission_type' do
|
|
it "should work for on paper assignments" do
|
|
assignment_model(:submission_types => 'on_paper', :course => @course)
|
|
expect(@assignment.readable_submission_types).to eq 'on paper'
|
|
end
|
|
end
|
|
|
|
describe '#update_grades_if_details_changed' do
|
|
before :once do
|
|
assignment_model(course: @course)
|
|
end
|
|
|
|
it "should update grades if points_possible changes" do
|
|
@assignment.context.expects(:recompute_student_scores).once
|
|
@assignment.points_possible = 3
|
|
@assignment.save!
|
|
end
|
|
|
|
it "should update grades if muted changes" do
|
|
@assignment.context.expects(:recompute_student_scores).once
|
|
@assignment.muted = true
|
|
@assignment.save!
|
|
end
|
|
|
|
it "should update grades if workflow_state changes" do
|
|
@assignment.context.expects(:recompute_student_scores).once
|
|
@assignment.unpublish
|
|
end
|
|
|
|
it "should not update grades otherwise" do
|
|
@assignment.context.expects(:recompute_student_scores).never
|
|
@assignment.title = 'hi'
|
|
@assignment.due_at = 1.hour.ago
|
|
@assignment.description = 'blah'
|
|
@assignment.save!
|
|
end
|
|
end
|
|
|
|
describe "#update_submission" do
|
|
let(:assignment) { assignment_model(course: @course) }
|
|
|
|
it "raises an error if original_student is nil" do
|
|
expect {
|
|
assignment.update_submission(nil)
|
|
}.to raise_error "Student Required"
|
|
end
|
|
|
|
it "raises an error if no submission is associated with the student" do
|
|
expect {
|
|
assignment.update_submission(@student)
|
|
}.to raise_error "No submission found for that student"
|
|
end
|
|
|
|
context "when the student is not in a group" do
|
|
let!(:associate_student_and_submission) {
|
|
assignment.submissions.create user: @student
|
|
}
|
|
let(:update_submission_response) { assignment.update_submission(@student) }
|
|
|
|
it "returns an Array" do
|
|
expect(update_submission_response.class).to eq Array
|
|
end
|
|
|
|
it "returns a collection of submissions" do
|
|
assignment.update_submission(@student).first
|
|
expect(update_submission_response.first.class).to eq Submission
|
|
end
|
|
end
|
|
|
|
context "when the student is in a group" do
|
|
let!(:create_a_group_with_a_submitted_assignment) {
|
|
setup_assignment_with_group
|
|
@assignment.submit_homework(@u1,
|
|
submission_type: "online_text_entry",
|
|
body: "Some text for you")
|
|
}
|
|
|
|
context "when a comment is submitted" do
|
|
let(:update_assignment_with_comment) {
|
|
@assignment.update_submission @u2,
|
|
"comment" => "WAT?",
|
|
"group_comment" => true,
|
|
user_id: @course.teachers.first.id
|
|
}
|
|
|
|
it "returns an Array" do
|
|
expect(update_assignment_with_comment.class).to eq Array
|
|
end
|
|
|
|
it "creates a comment for each student in the group" do
|
|
expect {
|
|
update_assignment_with_comment
|
|
}.to change{SubmissionComment.count}.by(@u1.groups.first.users.count)
|
|
end
|
|
|
|
it "creates comments with the same group_comment_id" do
|
|
update_assignment_with_comment
|
|
comments = SubmissionComment.last(@u1.groups.first.users.count)
|
|
expect(comments.first.group_comment_id).to eq comments.last.group_comment_id
|
|
end
|
|
end
|
|
|
|
context "when a comment is not submitted" do
|
|
it "returns an Array" do
|
|
expect(@assignment.update_submission(@u2).class).to eq Array
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "basic validation" do
|
|
|
|
describe "possible points" do
|
|
|
|
it "does not allow a negative value" do
|
|
assignment = Assignment.new(points_possible: -1)
|
|
assignment.valid?
|
|
expect(assignment.errors.keys.include?(:points_possible)).to be_truthy
|
|
end
|
|
|
|
it "allows a nil value" do
|
|
assignment = Assignment.new(points_possible: nil)
|
|
assignment.valid?
|
|
expect(assignment.errors.keys.include?(:points_possible)).to be_falsey
|
|
end
|
|
|
|
it "allows a 0 value" do
|
|
assignment = Assignment.new(points_possible: 0)
|
|
assignment.valid?
|
|
expect(assignment.errors.keys.include?(:points_possible)).to be_falsey
|
|
end
|
|
|
|
it "allows a positive value" do
|
|
assignment = Assignment.new(points_possible: 13)
|
|
assignment.valid?
|
|
expect(assignment.errors.keys.include?(:points_possible)).to be_falsey
|
|
end
|
|
|
|
it "does not attempt validation unless points_possible has changed" do
|
|
assignment = Assignment.new(points_possible: -13)
|
|
assignment.stubs(:points_possible_changed?).returns(false)
|
|
assignment.valid?
|
|
expect(assignment.errors.keys.include?(:points_possible)).to be_falsey
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
describe 'title validation' do
|
|
let(:assignment) { Assignment.new }
|
|
let(:errors) {
|
|
assignment.valid?
|
|
assignment.errors
|
|
}
|
|
|
|
it 'must allow a title equal to the maximum length' do
|
|
assignment.title = 'a' * Assignment.maximum_string_length
|
|
expect(errors[:title]).to be_empty
|
|
end
|
|
|
|
it 'must not allow a title longer than the maximum length' do
|
|
assignment.title = 'a' * (Assignment.maximum_string_length + 1)
|
|
expect(errors[:title]).not_to be_empty
|
|
end
|
|
|
|
it 'must allow a blank title when it is unchanged and was previously blank' do
|
|
assignment = @course.assignments.create!(assignment_valid_attributes)
|
|
assignment.title = ''
|
|
assignment.save(validate: false)
|
|
|
|
assignment.valid?
|
|
errors = assignment.errors
|
|
expect(errors[:title]).to be_empty
|
|
end
|
|
|
|
it 'must not allow the title to be blank if changed' do
|
|
assignment = @course.assignments.create!(assignment_valid_attributes)
|
|
assignment.title = ' '
|
|
assignment.valid?
|
|
errors = assignment.errors
|
|
expect(errors[:title]).not_to be_empty
|
|
end
|
|
end
|
|
|
|
describe "group category validation" do
|
|
before :once do
|
|
@group_category = @course.group_categories.create! name: "groups"
|
|
@groups = 2.times.map { |i|
|
|
@group_category.groups.create! name: "group #{i}", context: @course
|
|
}
|
|
end
|
|
|
|
let_once(:a1) { assignment }
|
|
|
|
def assignment(group_category = nil)
|
|
a = @course.assignments.build name: "test"
|
|
a.group_category = group_category
|
|
a.tap &:save!
|
|
end
|
|
|
|
it "lets you change group category attributes before homework is submitted" do
|
|
a1.group_category = @group_category
|
|
expect(a1).to be_valid
|
|
|
|
a2 = assignment(@group_category)
|
|
a2.group_category = nil
|
|
expect(a2).to be_valid
|
|
end
|
|
|
|
it "doesn't let you change group category attributes after homework is submitted" do
|
|
a1.submit_homework @student, body: "hello, world"
|
|
a1.group_category = @group_category
|
|
expect(a1).not_to be_valid
|
|
|
|
a2 = assignment(@group_category)
|
|
a2.submit_homework @student, body: "hello, world"
|
|
a2.group_category = nil
|
|
expect(a2).not_to be_valid
|
|
end
|
|
end
|
|
end
|
|
|
|
def setup_assignment_with_group
|
|
assignment_model(:group_category => "Study Groups", :course => @course)
|
|
@group = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)
|
|
@u1 = @a.context.enroll_user(User.create(:name => "user 1")).user
|
|
@u2 = @a.context.enroll_user(User.create(:name => "user 2")).user
|
|
@u3 = @a.context.enroll_user(User.create(:name => "user 3")).user
|
|
@group.add_user(@u1)
|
|
@group.add_user(@u2)
|
|
@assignment.reload
|
|
end
|
|
|
|
def setup_assignment_without_submission
|
|
assignment_model(:course => @course)
|
|
@assignment.reload
|
|
end
|
|
|
|
def setup_assignment_with_homework
|
|
setup_assignment_without_submission
|
|
res = @assignment.submit_homework(@user, {:submission_type => 'online_text_entry', :body => 'blah'})
|
|
expect(res).not_to be_nil
|
|
expect(res).to be_is_a(Submission)
|
|
@assignment.reload
|
|
end
|
|
|
|
def setup_assignment_with_students
|
|
@graded_notify = Notification.create!(:name => "Submission Graded")
|
|
@grade_change_notify = Notification.create!(:name => "Submission Grade Changed")
|
|
@stu1 = @student
|
|
@course.enroll_student(@stu2 = user)
|
|
@assignment = @course.assignments.create(:title => "asdf", :points_possible => 10)
|
|
@sub1 = @assignment.grade_student(@stu1, :grade => 9).first
|
|
expect(@sub1.score).to eq 9
|
|
# Took this out until it is asked for
|
|
# @sub1.published_score.should_not == @sub1.score
|
|
expect(@sub1.published_score).to eq @sub1.score
|
|
@assignment.reload
|
|
expect(@assignment.submissions).to be_include(@sub1)
|
|
end
|
|
|
|
def submit_homework(student)
|
|
a = Attachment.create! context: student,
|
|
filename: "homework.pdf",
|
|
uploaded_data: StringIO.new("blah blah blah")
|
|
@assignment.submit_homework(student, attachments: [a],
|
|
submission_type: "online_upload")
|
|
a
|
|
end
|
|
|
|
def zip_submissions
|
|
zip = Attachment.new filename: 'submissions.zip'
|
|
zip.user = @teacher
|
|
zip.workflow_state = 'to_be_zipped'
|
|
zip.context = @assignment
|
|
zip.save!
|
|
ContentZipper.process_attachment(zip, @teacher)
|
|
raise "zip failed" if zip.workflow_state != "zipped"
|
|
zip
|
|
end
|
|
|
|
def setup_differentiated_assignments(opts={})
|
|
if !opts[:course]
|
|
course_with_teacher(active_all: true, differentiated_assignments: true)
|
|
end
|
|
|
|
@section1 = @course.course_sections.create!(name: 'Section One')
|
|
@section2 = @course.course_sections.create!(name: 'Section Two')
|
|
|
|
if opts[:ta]
|
|
@ta = course_with_ta(course: @course, active_all: true).user
|
|
end
|
|
|
|
@student1, @student2, @student3 = create_users(3, return_type: :record)
|
|
student_in_section(@section1, user: @student1)
|
|
student_in_section(@section2, user: @student2)
|
|
|
|
@assignment = assignment_model(course: @course, submission_types: "online_url", workflow_state: "published")
|
|
@override_s1 = differentiated_assignment(assignment: @assignment, course_section: @section1)
|
|
@override_s1.due_at = 1.day.from_now
|
|
@override_s1.save!
|
|
end
|