canvas-lms/spec/models/submission_spec.rb

1786 lines
68 KiB
Ruby

#
# Copyright (C) 2011 - 2014 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper.rb')
require File.expand_path(File.dirname(__FILE__) + '/../lib/validates_as_url.rb')
describe Submission do
before(:once) do
course_with_student(active_all: true)
@context = @course
@assignment = @context.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@valid_attributes = {
:assignment_id => @assignment.id,
:user_id => @user.id,
:grade => "1.5",
:url => "www.instructure.com"
}
end
it "should create a new instance given valid attributes" do
Submission.create!(@valid_attributes)
end
include_examples "url validation tests"
it "should check url validity" do
test_url_validation(Submission.create!(@valid_attributes))
end
it "should add http:// to the body for long urls, too" do
s = Submission.create!(@valid_attributes)
expect(s.url).to eq 'http://www.instructure.com'
long_url = ("a"*300 + ".com")
s.url = long_url
s.save!
expect(s.url).to eq "http://#{long_url}"
# make sure it adds the "http://" to the body for long urls, too
expect(s.body).to eq "http://#{long_url}"
end
it "should offer the context, if one is available" do
@course = Course.new
@assignment = Assignment.new(:context => @course)
@assignment.expects(:context).returns(@course)
@submission = Submission.new
expect{@submission.context}.not_to raise_error
expect(@submission.context).to be_nil
@submission.assignment = @assignment
expect(@submission.context).to eql(@course)
end
it "should have an interesting state machine" do
submission_spec_model
expect(@submission.state).to eql(:submitted)
@submission.grade_it
expect(@submission.state).to eql(:graded)
end
it "should be versioned" do
submission_spec_model
expect(@submission).to be_respond_to(:versions)
end
it "should not save new versions by default" do
submission_spec_model
expect {
@submission.save!
}.not_to change(@submission.versions, :count)
end
describe "version indexing" do
it "should create a SubmissionVersion when a new submission is created" do
expect {
submission_spec_model
}.to change(SubmissionVersion, :count)
end
it "should create a SubmissionVersion when a new version is saved" do
submission_spec_model
expect {
@submission.with_versioning(:explicit => true) { @submission.save }
}.to change(SubmissionVersion, :count)
end
end
it "should ensure the media object exists" do
assignment_model
se = @course.enroll_student(user)
MediaObject.expects(:ensure_media_object).with("fake", { :context => se.user, :user => se.user })
@submission = @assignment.submit_homework(se.user, :media_comment_id => "fake", :media_comment_type => "audio")
end
it "should log submissions with grade changes" do
submission_spec_model
Auditors::GradeChange.expects(:record).once
@submission.score = 5
@submission.save!
@submission.grader_id = @user.id
@submission.save!
end
it "should log excused submissions" do
submission_spec_model
Auditors::GradeChange.expects(:record).once
@submission.excused = true
@submission.save!
@submission.grader_id = @user.id
@submission.save!
end
it "should log submissions affected by assignment update" do
submission_spec_model
Auditors::GradeChange.expects(:record).twice
# only graded submissions are updated by assignment
@submission.score = 111
@submission.save!
@assignment.points_possible = 999
@assignment.save!
end
context "#graded_anonymously" do
it "saves when grade changed and set explicitly" do
submission_spec_model
expect(@submission.graded_anonymously).to be_falsey
@submission.score = 42
@submission.graded_anonymously = true
@submission.save!
expect(@submission.graded_anonymously).to be_truthy
@submission.reload
expect(@submission.graded_anonymously).to be_truthy
end
it "retains its value when grade does not change" do
submission_spec_model(graded_anonymously: true, score: 3, grade: "3")
@submission = Submission.find(@submission.id) # need new model object
expect(@submission.graded_anonymously).to be_truthy
@submission.body = 'test body'
@submission.save!
@submission.reload
expect(@submission.graded_anonymously).to be_truthy
end
it "resets when grade changed and not set explicitly" do
submission_spec_model(graded_anonymously: true, score: 3, grade: "3")
@submission = Submission.find(@submission.id) # need new model object
expect(@submission.graded_anonymously).to be_truthy
@submission.score = 42
@submission.save!
@submission.reload
expect(@submission.graded_anonymously).to be_falsey
end
end
context "Discussion Topic" do
it "should use correct date for its submitted_at value" do
course_with_student_logged_in(:active_all => true)
@topic = @course.discussion_topics.create(:title => "some topic")
@assignment = @course.assignments.create(:title => "some discussion assignment")
@assignment.submission_types = 'discussion_topic'
@assignment.save!
@entry1 = @topic.discussion_entries.create(:message => "first entry", :user => @user)
@topic.assignment_id = @assignment.id
@topic.save!
@submission = @assignment.submissions.where(:user_id => @entry1.user_id).first
new_time = Time.now + 30.minutes
Time.stubs(:now).returns(new_time)
@entry2 = @topic.discussion_entries.create(:message => "second entry", :user => @user)
@submission.reload
expect((@submission.submitted_at.to_i - @submission.created_at.to_i).abs).to be < 1.minute
end
it "should not create multiple versions on submission for discussion topics" do
course_with_student_logged_in(:active_all => true)
@topic = @course.discussion_topics.create(:title => "some topic")
@assignment = @course.assignments.create(:title => "some discussion assignment")
@assignment.submission_types = 'discussion_topic'
@assignment.save!
@topic.assignment_id = @assignment.id
@topic.save!
Timecop.freeze(1.second.ago) do
@assignment.submit_homework(@student, :submission_type => 'discussion_topic')
end
@assignment.submit_homework(@student, :submission_type => 'discussion_topic')
expect(@student.submissions.first.submission_history.count).to eq 1
end
end
context "broadcast policy" do
context "Submission Notifications" do
before :once do
Notification.create(:name => 'Assignment Submitted')
Notification.create(:name => 'Assignment Resubmitted')
Notification.create(:name => 'Assignment Submitted Late')
Notification.create(:name => 'Group Assignment Submitted Late')
course_with_teacher(course: @course, active_all: true)
end
it "should send the correct message when an assignment is turned in on-time" do
@assignment.workflow_state = "published"
@assignment.update_attributes(:due_at => Time.now + 1000)
submission_spec_model(:user => @student)
expect(@submission.messages_sent.keys).to eq ['Assignment Submitted']
end
it "should send the correct message when an assignment is turned in late" do
@assignment.workflow_state = "published"
@assignment.update_attributes(:due_at => Time.now - 1000)
submission_spec_model(:user => @student)
expect(@submission.messages_sent.keys).to eq ['Assignment Submitted Late']
end
it "should send the correct message when an assignment is resubmitted on-time" do
@assignment.submission_types = ['online_text_entry']
@assignment.due_at = Time.now + 1000
@assignment.save!
@assignment.submit_homework(@student, :body => "lol")
resubmission = @assignment.submit_homework(@student, :body => "frd")
expect(resubmission.messages_sent.keys).to eq ['Assignment Resubmitted']
end
it "should send the correct message when an assignment is resubmitted late" do
@assignment.submission_types = ['online_text_entry']
@assignment.due_at = Time.now - 1000
@assignment.save!
@assignment.submit_homework(@student, :body => "lol")
resubmission = @assignment.submit_homework(@student, :body => "frd")
expect(resubmission.messages_sent.keys).to eq ['Assignment Submitted Late']
end
it "should send the correct message when a group assignment is submitted late" do
@a = assignment_model(:course => @context, :group_category => "Study Groups", :due_at => Time.now - 1000, :submission_types => ["online_text_entry"])
@group1 = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)
@group1.add_user(@student)
submission = @a.submit_homework @student, :submission_type => "online_text_entry", :body => "blah"
expect(submission.messages_sent.keys).to eq ['Group Assignment Submitted Late']
end
end
context "Submission Graded" do
before :once do
Notification.create(:name => 'Submission Graded', :category => 'TestImmediately')
end
it "should create a message when the assignment has been graded and published" do
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
@submission.reload
expect(@submission.assignment).to eql(@assignment)
expect(@submission.assignment.state).to eql(:published)
@submission.grade_it!
expect(@submission.messages_sent).to be_include('Submission Graded')
end
it "should not create a message for a soft-concluded student" do
submission_spec_model
@course.start_at = 2.weeks.ago
@course.conclude_at = 1.weeks.ago
@course.restrict_enrollments_to_course_dates = true
@course.save!
@cc = @user.communication_channels.create(:path => "somewhere")
@submission.reload
expect(@submission.assignment).to eql(@assignment)
expect(@submission.assignment.state).to eql(:published)
@submission.grade_it!
expect(@submission.messages_sent).to_not be_include('Submission Graded')
end
it "notifies observers" do
submission_spec_model
course_with_observer(course: @course, associated_user_id: @user.id, active_all: true, active_cc: true)
@submission.grade_it!
expect(@observer.email_channel.messages.length).to eq 1
end
it "should not create a message when a muted assignment has been graded and published" do
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
@assignment.mute!
@submission.reload
expect(@submission.assignment).to eql(@assignment)
expect(@submission.assignment.state).to eql(:published)
@submission.grade_it!
expect(@submission.messages_sent).not_to be_include "Submission Graded"
end
it "should not create a message when this is a quiz submission" do
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
@quiz = Quizzes::Quiz.create!(:context => @course)
@submission.quiz_submission = @quiz.generate_submission(@user)
@submission.save!
@submission.reload
expect(@submission.assignment).to eql(@assignment)
expect(@submission.assignment.state).to eql(:published)
@submission.grade_it!
expect(@submission.messages_sent).not_to include('Submission Graded')
end
it "should create a hidden stream_item_instance when muted, graded, and published" do
submission_spec_model
@cc = @user.communication_channels.create :path => "somewhere"
@assignment.mute!
expect {
@submission = @assignment.grade_student(@user, :grade => 10)[0]
}.to change StreamItemInstance, :count
expect(@user.stream_item_instances.last).to be_hidden
end
it "should hide any existing stream_item_instances when muted" do
submission_spec_model
@cc = @user.communication_channels.create :path => "somewhere"
expect {
@submission = @assignment.grade_student(@user, :grade => 10)[0]
}.to change StreamItemInstance, :count
expect(@user.stream_item_instances.last).not_to be_hidden
@assignment.mute!
expect(@user.stream_item_instances.last).to be_hidden
end
it "should not create a message for admins and teachers with quiz submissions" do
course_with_teacher(:active_all => true)
assignment = @course.assignments.create!(
:title => 'assignment',
:points_possible => 10)
quiz = @course.quizzes.build(
:assignment_id => assignment.id,
:title => 'test quiz',
:points_possible => 10)
quiz.workflow_state = 'available'
quiz.save!
user = account_admin_user
channel = user.communication_channels.create!(:path => 'admin@example.com')
submission = quiz.generate_submission(user, false)
Quizzes::SubmissionGrader.new(submission).grade_submission
channel2 = @teacher.communication_channels.create!(:path => 'chang@example.com')
submission2 = quiz.generate_submission(@teacher, false)
Quizzes::SubmissionGrader.new(submission2).grade_submission
expect(submission.submission.messages_sent).not_to be_include('Submission Graded')
expect(submission2.submission.messages_sent).not_to be_include('Submission Graded')
end
end
it "should create a stream_item_instance when graded and published" do
Notification.create :name => "Submission Graded"
submission_spec_model
@cc = @user.communication_channels.create :path => "somewhere"
expect {
@assignment.grade_student(@user, :grade => 10)
}.to change StreamItemInstance, :count
end
it "should create a stream_item_instance when graded, and then made it visible when unmuted" do
Notification.create :name => "Submission Graded"
submission_spec_model
@cc = @user.communication_channels.create :path => "somewhere"
@assignment.mute!
expect {
@assignment.grade_student(@user, :grade => 10)
}.to change StreamItemInstance, :count
@assignment.unmute!
stream_item_ids = StreamItem.where(:asset_type => 'Submission', :asset_id => @assignment.submissions.all).pluck(:id)
stream_item_instances = StreamItemInstance.where(:stream_item_id => stream_item_ids)
stream_item_instances.each { |sii| expect(sii).not_to be_hidden }
end
context "Submission Grade Changed" do
it "should create a message when the score is changed and the grades were already published" do
Notification.create(:name => 'Submission Grade Changed')
@assignment.stubs(:score_to_grade).returns("10.0")
@assignment.stubs(:due_at).returns(Time.now - 100)
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
s = @assignment.grade_student(@user, :grade => 10)[0] #@submission
s.graded_at = Time.parse("Jan 1 2000")
s.save
@submission = @assignment.grade_student(@user, :grade => 9)[0]
expect(@submission).to eql(s)
expect(@submission.messages_sent).to be_include('Submission Grade Changed')
end
it 'doesnt create a grade changed message when theres a quiz attached' do
Notification.create(:name => 'Submission Grade Changed')
@assignment.stubs(:score_to_grade).returns("10.0")
@assignment.stubs(:due_at).returns(Time.now - 100)
submission_spec_model
@quiz = Quizzes::Quiz.create!(:context => @course)
@submission.quiz_submission = @quiz.generate_submission(@user)
@submission.save!
@cc = @user.communication_channels.create(:path => "somewhere")
s = @assignment.grade_student(@user, :grade => 10)[0] #@submission
s.graded_at = Time.parse("Jan 1 2000")
s.save
@submission = @assignment.grade_student(@user, :grade => 9)[0]
expect(@submission).to eql(s)
expect(@submission.messages_sent).not_to include('Submission Grade Changed')
end
it "should create a message when the score is changed and the grades were already published" do
Notification.create(:name => 'Submission Grade Changed')
Notification.create(:name => 'Submission Graded')
@assignment.stubs(:score_to_grade).returns("10.0")
@assignment.stubs(:due_at).returns(Time.now - 100)
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
s = @assignment.grade_student(@user, :grade => 10)[0] #@submission
@submission = @assignment.grade_student(@user, :grade => 9)[0]
expect(@submission).to eql(s)
expect(@submission.messages_sent).not_to be_include('Submission Grade Changed')
expect(@submission.messages_sent).to be_include('Submission Graded')
end
it "should not create a message when the score is changed and the grades were already published for a muted assignment" do
Notification.create(:name => 'Submission Grade Changed')
@assignment.mute!
@assignment.stubs(:score_to_grade).returns("10.0")
@assignment.stubs(:due_at).returns(Time.now - 100)
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
s = @assignment.grade_student(@user, :grade => 10)[0] #@submission
s.graded_at = Time.parse("Jan 1 2000")
s.save
@submission = @assignment.grade_student(@user, :grade => 9)[0]
expect(@submission).to eql(s)
expect(@submission.messages_sent).not_to be_include('Submission Grade Changed')
end
it "should NOT create a message when the score is changed and the submission was recently graded" do
Notification.create(:name => 'Submission Grade Changed')
@assignment.stubs(:score_to_grade).returns("10.0")
@assignment.stubs(:due_at).returns(Time.now - 100)
submission_spec_model
@cc = @user.communication_channels.create(:path => "somewhere")
s = @assignment.grade_student(@user, :grade => 10)[0] #@submission
@submission = @assignment.grade_student(@user, :grade => 9)[0]
expect(@submission).to eql(s)
expect(@submission.messages_sent).not_to be_include('Submission Grade Changed')
end
end
end
context "turnitin" do
context "Turnitin LTI" do
let(:lti_tii_data) do
{
"attachment_42" => {
:status => "error",
:outcome_response => {
"outcomes_tool_placement_url" => "https://api.turnitin.com/api/lti/1p0/invalid?lang=en_us",
"paperid" => "607954245",
"lis_result_sourcedid" => "10-5-42-8-invalid"
},
:public_error_message => "Turnitin has not returned a score after 11 attempts to retrieve one."
}
}
end
let(:submission) { Submission.new }
describe "#turnitinable_by_lti?" do
it 'returns true if there is an associated lti tool and data stored' do
submission.turnitin_data = lti_tii_data
expect(submission.turnitinable_by_lti?).to be true
end
end
describe "#resubmit_lti_tii" do
let(:tool) do
@course.context_external_tools.create(
name: "a",
consumer_key: '12345',
shared_secret: 'secret',
url: 'http://example.com/launch')
end
it 'resubmits errored tii attachments' do
a = @course.assignments.create!(title: "test",
submission_types: 'external_tool',
external_tool_tag_attributes: {url: tool.url})
submission.assignment = a
submission.turnitin_data = lti_tii_data
submission.user = @user
outcome_response_processor_mock = mock('outcome_response_processor')
outcome_response_processor_mock.expects(:resubmit).with(submission, "attachment_42")
Turnitin::OutcomeResponseProcessor.stubs(:new).returns(outcome_response_processor_mock)
submission.retrieve_lti_tii_score
end
end
end
context "submission" do
def init_turnitin_api
@turnitin_api = Turnitin::Client.new('test_account', 'sekret')
@submission.context.expects(:turnitin_settings).at_least(1).returns([:placeholder])
Turnitin::Client.expects(:new).at_least(1).with(:placeholder).returns(@turnitin_api)
end
before(:once) do
@assignment.submission_types = "online_upload,online_text_entry"
@assignment.turnitin_enabled = true
@assignment.turnitin_settings = @assignment.turnitin_settings
@assignment.save!
@submission = @assignment.submit_homework(@user, { :body => "hello there", :submission_type => 'online_text_entry' })
end
it "should submit to turnitin after a delay" do
job = Delayed::Job.list_jobs(:future, 100).find { |j| j.tag == 'Submission#submit_to_turnitin' }
expect(job).not_to be_nil
expect(job.run_at).to be > Time.now.utc
end
it "should initially set turnitin submission to pending" do
init_turnitin_api
@turnitin_api.expects(:createOrUpdateAssignment).with(@assignment, @assignment.turnitin_settings).returns({ :assignment_id => "1234" })
@turnitin_api.expects(:enrollStudent).with(@context, @user).returns(stub(:success? => true))
@turnitin_api.expects(:submitPaper).returns({
@submission.asset_string => {
:object_id => '12345'
}
})
@submission.submit_to_turnitin
expect(@submission.reload.turnitin_data[@submission.asset_string][:status]).to eq 'pending'
end
it "should schedule a retry if something fails initially" do
init_turnitin_api
@turnitin_api.expects(:createOrUpdateAssignment).with(@assignment, @assignment.turnitin_settings).returns({ :assignment_id => "1234" })
@turnitin_api.expects(:enrollStudent).with(@context, @user).returns(stub(:success? => false))
@submission.submit_to_turnitin
expect(Delayed::Job.list_jobs(:future, 100).find_all { |j| j.tag == 'Submission#submit_to_turnitin' }.size).to eq 2
end
it "should set status as failed if something fails after several attempts" do
init_turnitin_api
@assignment.expects(:create_in_turnitin).returns(false)
@turnitin_api.expects(:enrollStudent).with(@context, @user).returns(stub(:success? => false, :error? => true, :error_hash => {}))
@turnitin_api.expects(:submitPaper).never
@submission.submit_to_turnitin(Submission::TURNITIN_RETRY)
expect(@submission.reload.turnitin_data[:status]).to eq 'error'
end
it "should set status back to pending on retry" do
init_turnitin_api
# first a submission, to get us into failed state
@assignment.expects(:create_in_turnitin).returns(false)
@turnitin_api.expects(:enrollStudent).with(@context, @user).returns(stub(:success? => false, :error? => true, :error_hash => {}))
@turnitin_api.expects(:submitPaper).never
@submission.submit_to_turnitin(Submission::TURNITIN_RETRY)
expect(@submission.reload.turnitin_data[:status]).to eq 'error'
# resubmit
@submission.resubmit_to_turnitin
expect(@submission.reload.turnitin_data[:status]).to be_nil
expect(@submission.turnitin_data[@submission.asset_string][:status]).to eq 'pending'
end
it "should set status to scored on success" do
init_turnitin_api
@submission.turnitin_data ||= {}
@submission.turnitin_data[@submission.asset_string] = { :object_id => '1234', :status => 'pending' }
@turnitin_api.expects(:generateReport).with(@submission, @submission.asset_string).returns({
:similarity_score => 56,
:web_overlap => 22,
:publication_overlap => 0,
:student_overlap => 33
})
@submission.check_turnitin_status
expect(@submission.reload.turnitin_data[@submission.asset_string][:status]).to eq 'scored'
end
it "should set status as failed if something fails after several attempts" do
init_turnitin_api
@submission.turnitin_data ||= {}
@submission.turnitin_data[@submission.asset_string] = { :object_id => '1234', :status => 'pending' }
@turnitin_api.expects(:generateReport).with(@submission, @submission.asset_string).returns({})
expects_job_with_tag('Submission#check_turnitin_status') do
@submission.check_turnitin_status(Submission::TURNITIN_STATUS_RETRY-1)
expect(@submission.reload.turnitin_data[@submission.asset_string][:status]).to eq 'pending'
end
@submission.check_turnitin_status(Submission::TURNITIN_STATUS_RETRY)
@submission.reload
updated_data = @submission.turnitin_data[@submission.asset_string]
expect(updated_data[:status]).to eq 'error'
end
it "should check status for all assets" do
init_turnitin_api
@submission.turnitin_data ||= {}
@submission.turnitin_data[@submission.asset_string] = { :object_id => '1234', :status => 'pending' }
@submission.turnitin_data["other_asset"] = { :object_id => 'xxyy', :status => 'pending' }
@turnitin_api.expects(:generateReport).with(@submission, @submission.asset_string).returns({
:similarity_score => 56, :web_overlap => 22, :publication_overlap => 0, :student_overlap => 33
})
@turnitin_api.expects(:generateReport).with(@submission, "other_asset").returns({ :similarity_score => 20 })
@submission.check_turnitin_status
@submission.reload
expect(@submission.turnitin_data[@submission.asset_string][:status]).to eq 'scored'
expect(@submission.turnitin_data["other_asset"][:status]).to eq 'scored'
end
it "should not blow up if submission_type has changed when job runs" do
@submission.submission_type = 'online_url'
@submission.context.expects(:turnitin_settings).never
expect { @submission.submit_to_turnitin }.not_to raise_error
end
end
describe "group" do
before(:once) do
@teacher = User.create(:name => "some teacher")
@student = User.create(:name => "a student")
@student1 = User.create(:name => "student 1")
@context.enroll_teacher(@teacher)
@context.enroll_student(@student)
@context.enroll_student(@student1)
@a = assignment_model(:course => @context, :group_category => "Study Groups")
@a.submission_types = "online_upload,online_text_entry"
@a.turnitin_enabled = true
@a.save!
@group1 = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)
@group1.add_user(@student)
@group1.add_user(@student1)
end
it "should submit to turnitin for the original submitter" do
submission = @a.submit_homework @student, :submission_type => "online_text_entry", :body => "blah"
Submission.where(assignment_id: @a).each do |s|
if s.id == submission.id
expect(s.turnitin_data[:last_processed_attempt]).to be > 0
else
expect(s.turnitin_data).to eq({})
end
end
end
end
context "report" do
before :once do
@assignment.submission_types = "online_upload,online_text_entry"
@assignment.turnitin_enabled = true
@assignment.turnitin_settings = @assignment.turnitin_settings
@assignment.save!
@submission = @assignment.submit_homework(@user, { :body => "hello there", :submission_type => 'online_text_entry' })
@submission.turnitin_data = {
"submission_#{@submission.id}" => {
:web_overlap => 92,
:error => true,
:publication_overlap => 0,
:state => "failure",
:object_id => "123456789",
:student_overlap => 90,
:similarity_score => 92
}
}
@submission.save!
end
before :each do
api = Turnitin::Client.new('test_account', 'sekret')
Turnitin::Client.expects(:new).at_least(1).returns(api)
api.expects(:sendRequest).with(:generate_report, 1, has_entries(:oid => "123456789")).at_least(1).returns('http://foo.bar')
end
it "should let teachers view the turnitin report" do
@teacher = User.create
@context.enroll_teacher(@teacher)
expect(@submission).to be_grants_right(@teacher, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @teacher)).not_to be_nil
end
it "should let students view the turnitin report after grading" do
@assignment.turnitin_settings[:originality_report_visibility] = 'after_grading'
@assignment.save!
@submission.reload
expect(@submission).not_to be_grants_right(@user, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @user)).to be_nil
@submission.score = 1
@submission.grade_it!
AdheresToPolicy::Cache.clear
expect(@submission).to be_grants_right(@user, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @user)).not_to be_nil
end
it "should let students view the turnitin report immediately if the visibility setting allows it" do
@assignment.turnitin_settings[:originality_report_visibility] = 'after_grading'
@assignment.save
@submission.reload
expect(@submission).not_to be_grants_right(@user, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @user)).to be_nil
@assignment.turnitin_settings[:originality_report_visibility] = 'immediate'
@assignment.save
@submission.reload
AdheresToPolicy::Cache.clear
expect(@submission).to be_grants_right(@user, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @user)).not_to be_nil
end
it "should let students view the turnitin report after the due date if the visibility setting allows it" do
@assignment.turnitin_settings[:originality_report_visibility] = 'after_due_date'
@assignment.due_at = Time.now + 1.day
@assignment.save
@submission.reload
expect(@submission).not_to be_grants_right(@user, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @user)).to be_nil
@assignment.due_at = Time.now - 1.day
@assignment.save
@submission.reload
AdheresToPolicy::Cache.clear
expect(@submission).to be_grants_right(@user, nil, :view_turnitin_report)
expect(@submission.turnitin_report_url("submission_#{@submission.id}", @user)).not_to be_nil
end
end
end
context '#external_tool_url' do
let(:submission) { Submission.new }
let(:lti_submission) { @assignment.submit_homework @user, submission_type: 'basic_lti_launch', url: 'http://www.example.com' }
context 'submission_type of "basic_lti_launch"' do
it 'returns a url containing the submitted url' do
expect(lti_submission.external_tool_url).to eq(lti_submission.url)
end
end
context 'submission_type of anything other than "basic_lti_launch"' do
it 'returns nothing' do
expect(submission.external_tool_url).to be_nil
end
end
end
it "should return the correct quiz_submission_version" do
# see redmine #6048
# set up the data to have a submission with a quiz submission with multiple versions
course
quiz = @course.quizzes.create!
quiz_submission = quiz.generate_submission @user, false
quiz_submission.save
submission = Submission.create!({
:assignment_id => @assignment.id,
:user_id => @user.id,
:quiz_submission_id => quiz_submission.id
})
submission = @assignment.submit_homework @user, :submission_type => 'online_quiz'
submission.quiz_submission_id = quiz_submission.id
# set the microseconds of the submission.submitted_at to be less than the
# quiz_submission.finished_at.
# first set them to be exactly the same (with microseconds)
time_to_i = submission.submitted_at.to_i
usec = submission.submitted_at.usec
timestamp = "#{time_to_i}.#{usec}".to_f
quiz_submission.finished_at = Time.at(timestamp)
quiz_submission.save
# get the data in a strange state where the quiz_submission.finished_at is
# microseconds older than the submission (caused the bug in #6048)
quiz_submission.finished_at = Time.at(timestamp + 0.00001)
quiz_submission.save
# verify the data is weird, to_i says they are equal, but the usecs are off
expect(quiz_submission.finished_at.to_i).to eq submission.submitted_at.to_i
expect(quiz_submission.finished_at.usec).to be > submission.submitted_at.usec
# create the versions that Submission#quiz_submission_version uses
quiz_submission.with_versioning do
quiz_submission.save
quiz_submission.save
end
# the real test, quiz_submission_version shouldn't care about usecs
expect(submission.reload.quiz_submission_version).to eq 2
end
it "should return only comments readable by the user" do
course_with_teacher(:active_all => true)
@student1 = student_in_course(:active_user => true).user
@student2 = student_in_course(:active_user => true).user
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.submission_types = "online_text_entry"
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@student1, :body => 'some message')
sc1 = SubmissionComment.create!(:submission => @submission, :author => @teacher, :comment => "a")
sc2 = SubmissionComment.create!(:submission => @submission, :author => @teacher, :comment => "b", :hidden => true)
sc3 = SubmissionComment.create!(:submission => @submission, :author => @student1, :comment => "c")
sc4 = SubmissionComment.create!(:submission => @submission, :author => @student2, :comment => "d")
SubmissionComment.create!(:submission => @submission, :author => @teacher, :comment => "e", :draft => true)
@submission.reload
@submission.limit_comments(@teacher)
expect(@submission.submission_comments.count).to eql 5
expect(@submission.visible_submission_comments.count).to eql 3
@submission.limit_comments(@student1)
expect(@submission.submission_comments.count).to eql 3
expect(@submission.visible_submission_comments.count).to eql 3
@submission.limit_comments(@student2)
expect(@submission.submission_comments.count).to eql 1
expect(@submission.visible_submission_comments.count).to eql 1
end
describe "read/unread state" do
it "should be read if a submission exists with no grade" do
@submission = @assignment.submit_homework(@user)
expect(@submission.read?(@user)).to be_truthy
end
it "should be unread after assignment is graded" do
@submission = @assignment.grade_student(@user, { :grade => 3 }).first
expect(@submission.unread?(@user)).to be_truthy
end
it "should be unread after submission is graded" do
@assignment.submit_homework(@user)
@submission = @assignment.grade_student(@user, { :grade => 3 }).first
expect(@submission.unread?(@user)).to be_truthy
end
it "should be unread after submission is commented on by teacher" do
@student = @user
course_with_teacher(:course => @context, :active_all => true)
@submission = @assignment.update_submission(@student, { :commenter => @teacher, :comment => "good!" }).first
expect(@submission.unread?(@user)).to be_truthy
end
it "should be read if other submission fields change" do
@submission = @assignment.submit_homework(@user)
@submission.workflow_state = 'graded'
@submission.graded_at = Time.now
@submission.save!
expect(@submission.read?(@user)).to be_truthy
end
end
describe "mute" do
let(:submission) { Submission.new }
before :each do
submission.published_score = 100
submission.published_grade = 'A'
submission.graded_at = Time.now
submission.grade = 'B'
submission.score = 90
submission.mute
end
specify { expect(submission.published_score).to be_nil }
specify { expect(submission.published_grade).to be_nil }
specify { expect(submission.graded_at).to be_nil }
specify { expect(submission.grade).to be_nil }
specify { expect(submission.score).to be_nil }
end
describe "muted_assignment?" do
it "returns true if assignment is muted" do
assignment = stub(:muted? => true)
@submission = Submission.new
@submission.expects(:assignment).returns(assignment)
expect(@submission.muted_assignment?).to eq true
end
it "returns false if assignment is not muted" do
assignment = stub(:muted? => false)
@submission = Submission.new
@submission.expects(:assignment).returns(assignment)
expect(@submission.muted_assignment?).to eq false
end
end
describe "without_graded_submission?" do
let(:submission) { Submission.new }
it "returns false if submission does not has_submission?" do
submission.stubs(:has_submission?).returns false
submission.stubs(:graded?).returns true
expect(submission.without_graded_submission?).to eq false
end
it "returns false if submission does is not graded" do
submission.stubs(:has_submission?).returns true
submission.stubs(:graded?).returns false
expect(submission.without_graded_submission?).to eq false
end
it "returns true if submission is not graded and has no submission" do
submission.stubs(:has_submission?).returns false
submission.stubs(:graded?).returns false
expect(submission.without_graded_submission?).to eq true
end
end
describe "graded?" do
it "is false before graded" do
submission, _ = @assignment.find_or_create_submission(@user)
expect(submission).to_not be_graded
end
it "is true for graded assignments" do
submission, _ = @assignment.grade_student(@user, grade: 1)
expect(submission).to be_graded
end
it "is also true for excused assignments" do
submission, _ = @assignment.find_or_create_submission(@user)
submission.excused = true
expect(submission).to be_graded
end
end
describe "autograded" do
let(:submission) { Submission.new }
it "returns false when its not autograded" do
submission = Submission.new
expect(submission).to_not be_autograded
submission.grader_id = Shard.global_id_for(@user.id)
expect(submission).to_not be_autograded
end
it "returns true when its autograded" do
submission = Submission.new
submission.grader_id = -1
expect(submission).to be_autograded
end
end
describe "past_due" do
before :once do
submission_spec_model
@submission1 = @submission
add_section('overridden section')
u2 = student_in_section(@course_section, :active_all => true)
submission_spec_model(:user => u2)
@submission2 = @submission
@assignment.update_attribute(:due_at, Time.zone.now - 1.day)
@submission1.reload
@submission2.reload
end
it "should update when an assignment's due date is changed" do
expect(@submission1).to be_past_due
@assignment.reload.update_attribute(:due_at, Time.zone.now + 1.day)
expect(@submission1.reload).not_to be_past_due
end
it "should update when an applicable override is changed" do
expect(@submission1).to be_past_due
expect(@submission2).to be_past_due
assignment_override_model :assignment => @assignment,
:due_at => Time.zone.now + 1.day,
:set => @course_section
expect(@submission1.reload).to be_past_due
expect(@submission2.reload).not_to be_past_due
end
it "should give a quiz submission 30 extra seconds before making it past due" do
quiz_with_graded_submission([{:question_data => {:name => 'question 1', :points_possible => 1, 'question_type' => 'essay_question'}}]) do
{
"text_after_answers" => "",
"question_#{@questions[0].id}" => "<p>Lorem ipsum answer.</p>",
"context_id" => "#{@course.id}",
"context_type" => "Course",
"user_id" => "#{@user.id}",
"quiz_id" => "#{@quiz.id}",
"course_id" => "#{@course.id}",
"question_text" => "Lorem ipsum question",
}
end
@assignment.due_at = "20130101T23:59Z"
@assignment.save!
submission = @quiz_submission.submission.reload
submission.write_attribute(:submitted_at, @assignment.due_at + 3.days)
expect(submission).to be_past_due
submission.write_attribute(:submitted_at, @assignment.due_at + 30.seconds)
expect(submission).not_to be_past_due
end
end
describe "late" do
before :once do
submission_spec_model
end
it "should be false if not past due" do
@submission.submitted_at = 2.days.ago
@submission.cached_due_date = 1.day.ago
expect(@submission).not_to be_late
end
it "should be false if not submitted, even if past due" do
@submission.submission_type = nil # forces submitted_at to be nil
@submission.cached_due_date = 1.day.ago
expect(@submission).not_to be_late
end
it "should be true if submitted and past due" do
@submission.submitted_at = 1.day.ago
@submission.cached_due_date = 2.days.ago
expect(@submission).to be_late
end
end
describe "missing" do
before :once do
submission_spec_model
end
it "should be false if not past due" do
@submission.submitted_at = 2.days.ago
@submission.cached_due_date = 1.day.ago
expect(@submission).not_to be_missing
end
it "should be false if submitted, even if past due" do
@submission.submitted_at = 1.day.ago
@submission.cached_due_date = 2.days.ago
expect(@submission).not_to be_missing
end
it "should be true if not submitted, past due, and expects a submission" do
@submission.assignment.submission_types = "online_quiz"
@submission.submission_type = nil # forces submitted_at to be nil
@submission.cached_due_date = 1.day.ago
# Regardless of score
@submission.score = 0.00000001
@submission.graded_at = Time.zone.now + 1.day
expect(@submission).to be_missing
end
it "should be true if not submitted, score of zero, and does not expect a submission" do
@submission.assignment.submission_types = "on_paper"
@submission.submission_type = nil # forces submitted_at to be nil
@submission.cached_due_date = 1.day.ago
@submission.score = 0
@submission.graded_at = Time.zone.now + 1.day
expect(@submission).to be_missing
end
it "should be false if not submitted, score greater than zero, and does not expect a submission" do
@submission.assignment.submission_types = "on_paper"
@submission.submission_type = nil # forces submitted_at to be nil
@submission.cached_due_date = 1.day.ago
@submission.score = 0.00000001
@submission.graded_at = Time.zone.now + 1.day
expect(@submission).to be_missing
end
end
describe "cached_due_date" do
it "should get initialized during submission creation" do
# create an invited user, so that the submission is not automatically
# created by the DueDateCacher
student_in_course
@assignment.update_attribute(:due_at, Time.zone.now - 1.day)
override = @assignment.assignment_overrides.build
override.title = "Some Title"
override.set = @course.default_section
override.override_due_at(Time.zone.now + 1.day)
override.save!
# mysql just truncated the timestamp
override.reload
submission = @assignment.submissions.create(:user => @user)
expect(submission.cached_due_date).to eq override.due_at
end
end
describe "update_attachment_associations" do
before do
course_with_student active_all: true
@assignment = @course.assignments.create!
end
it "doesn't include random attachment ids" do
f = Attachment.create! uploaded_data: StringIO.new('blah'),
context: @course,
filename: 'blah.txt'
sub = @assignment.submit_homework(@user, attachments: [f])
expect(sub.attachments).to eq []
end
end
describe "versioned_attachments" do
it "should include user attachments" do
student_in_course(active_all: true)
att = attachment_model(filename: "submission.doc", :context => @student)
sub = @assignment.submit_homework(@student, attachments: [att])
expect(sub.versioned_attachments).to eq [att]
end
it "should not include attachments with a context of Submission" do
student_in_course(active_all: true)
att = attachment_model(filename: "submission.doc", :context => @student)
sub = @assignment.submit_homework(@student, attachments: [att])
sub.attachments.update_all(:context_type => "Submission", :context_id => sub.id)
expect(sub.reload.versioned_attachments).to be_empty
end
end
describe "includes_attachment?" do
it "includes current attachments" do
spoiler = attachment_model(context: @student)
attachment_model context: @student
sub = @assignment.submit_homework @student, attachments: [@attachment]
expect(sub.attachments).to eq([@attachment])
expect(sub.includes_attachment?(spoiler)).to eq false
expect(sub.includes_attachment?(@attachment)).to eq true
end
it "includes attachments to previous versions" do
old_attachment_1 = attachment_model(context: @student)
old_attachment_2 = attachment_model(context: @student)
sub = @assignment.submit_homework @student, attachments: [old_attachment_1, old_attachment_2]
attachment_model context: @student
sub = @assignment.submit_homework @student, attachments: [@attachment]
expect(sub.attachments).to eq([@attachment])
expect(sub.includes_attachment?(old_attachment_1)).to eq true
expect(sub.includes_attachment?(old_attachment_2)).to eq true
end
end
context "bulk loading" do
def ensure_attachments_arent_queried
Attachment.expects(:where).never
end
def submission_for_some_user
student_in_course active_all: true
@assignment.submit_homework(@student,
submission_type: "online_url",
url: "http://example.com")
end
describe "#bulk_load_versioned_attachments" do
it "loads attachments for many submissions at once" do
attachments = []
submissions = 3.times.map do |i|
student_in_course(active_all: true)
attachments << [
attachment_model(filename: "submission#{i}-a.doc", :context => @student),
attachment_model(filename: "submission#{i}-b.doc", :context => @student)
]
@assignment.submit_homework @student, attachments: attachments[i]
end
Submission.bulk_load_versioned_attachments(submissions)
ensure_attachments_arent_queried
submissions.each_with_index do |s, i|
expect(s.versioned_attachments).to eq attachments[i]
end
end
it "includes url submission attachments" do
s = submission_for_some_user
s.attachment = attachment_model(filename: "screenshot.jpg",
context: @student)
Submission.bulk_load_versioned_attachments([s])
ensure_attachments_arent_queried
expect(s.versioned_attachments).to eq [s.attachment]
end
it "handles bad data" do
s = submission_for_some_user
s.update_attribute(:attachment_ids, '99999999')
Submission.bulk_load_versioned_attachments([s])
expect(s.versioned_attachments).to eq []
end
it "handles submission histories with different attachments" do
student_in_course(active_all: true)
attachments = [attachment_model(filename: "submission-a.doc", :context => @student)]
Timecop.freeze(10.second.ago) do
@assignment.submit_homework(@student, submission_type: 'online_upload',
attachments: [attachments[0]])
end
attachments << attachment_model(filename: "submission-b.doc", :context => @student)
Timecop.freeze(5.second.ago) do
@assignment.submit_homework @student, attachments: [attachments[1]]
end
attachments << attachment_model(filename: "submission-c.doc", :context => @student)
Timecop.freeze(1.second.ago) do
@assignment.submit_homework @student, attachments: [attachments[2]]
end
submission = @assignment.submission_for_student(@student)
Submission.bulk_load_versioned_attachments(submission.submission_history)
submission.submission_history.each_with_index do |s, index|
expect(s.attachment_ids.to_i).to eq attachments[index].id
end
end
end
describe "#bulk_load_attachments_for_submissions" do
it "loads attachments for many submissions at once and returns a hash" do
expected_attachments_for_submissions = {}
submissions = 3.times.map do |i|
student_in_course(active_all: true)
attachment = [attachment_model(filename: "submission#{i}.doc", :context => @student)]
sub = @assignment.submit_homework @student, attachments: attachment
expected_attachments_for_submissions[sub] = attachment
sub
end
result = Submission.bulk_load_attachments_for_submissions(submissions)
ensure_attachments_arent_queried
expect(result).to eq(expected_attachments_for_submissions)
end
it "handles bad data" do
s = submission_for_some_user
s.update_attribute(:attachment_ids, '99999999')
expected_attachments_for_submissions = { s => [] }
result = Submission.bulk_load_attachments_for_submissions(s)
expect(result).to eq(expected_attachments_for_submissions)
end
end
end
describe "#assign_assessor" do
def peer_review_assignment
assignment = @course.assignments.build(title: 'Peer review',
due_at: Time.now - 1.day,
points_possible: 5,
submission_types: 'online_text_entry')
assignment.peer_reviews_assigned = true
assignment.peer_reviews = true
assignment.automatic_peer_reviews = true
assignment.save!
assignment
end
before(:each) do
student_in_course(active_all: true)
@student2 = user
@course.enroll_student(@student2).accept!
@assignment = peer_review_assignment
@assignment.submit_homework(@student, body: 'Lorem ipsum dolor')
@assignment.submit_homework(@student2, body: 'Sit amet consectetuer')
end
it "should send a reminder notification" do
AssessmentRequest.any_instance.expects(:send_reminder!).once
submission1, submission2 = @assignment.submissions
submission1.assign_assessor(submission2)
end
end
describe "#get_web_snapshot" do
it "should not blow up if web snapshotting fails" do
sub = Submission.new(@valid_attributes)
CutyCapt.expects(:enabled?).returns(true)
CutyCapt.expects(:snapshot_attachment_for_url).with(sub.url).returns(nil)
sub.get_web_snapshot
end
end
describe '#submit_attachments_to_canvadocs' do
it 'creates crocodoc documents' do
Canvas::Crocodoc.stubs(:enabled?).returns true
s = @assignment.submit_homework(@user,
submission_type: "online_text_entry",
body: "hi")
# creates crocodoc documents
a1 = crocodocable_attachment_model context: @user
s.attachments = [a1]
s.save
cd = a1.crocodoc_document
expect(cd).not_to be_nil
# shouldn't mess with existing crocodoc documents
a2 = crocodocable_attachment_model context: @user
s.attachments = [a1, a2]
s.save
expect(a1.crocodoc_document(true)).to eq cd
expect(a2.crocodoc_document).to eq a2.crocodoc_document
end
context "canvadocs_submissions records" do
before(:once) do
@student1, @student2 = n_students_in_course(2)
@attachment = crocodocable_attachment_model(context: @student1)
@assignment = @course.assignments.create! name: "A1",
submission_types: "online_upload"
end
before do
Canvadocs.stubs(:enabled?).returns true
Canvadocs.stubs(:annotations_supported?).returns true
Canvadocs.stubs(:config).returns {}
end
it "ties submissions to canvadocs" do
s = @assignment.submit_homework(@student1,
submission_type: "online_upload",
attachments: [@attachment])
expect(s.canvadocs).to eq [@attachment.canvadoc]
end
it "create records for each group submission" do
gc = @course.group_categories.create! name: "Project Groups"
group = gc.groups.create! name: "A Team", context: @course
group.add_user(@student1)
group.add_user(@student2)
@assignment.update_attribute :group_category, gc
@assignment.submit_homework(@student1,
submission_type: "online_upload",
attachments: [@attachment])
[@student1, @student2].each do |student|
submission = @assignment.submission_for_student(student)
expect(submission.canvadocs).to eq [@attachment.canvadoc]
end
end
context 'preferred plugin course id' do
let(:submit_homework) do
->() do
@assignment.submit_homework(@student1,
submission_type: "online_upload",
attachments: [@attachment])
end
end
it 'sets preferred plugin course id to the course ID' do
s = submit_homework.call
expect(s.canvadocs.first.preferred_plugin_course_id).to eq(@course.id.to_s)
end
end
end
it "doesn't create jobs for non-previewable documents" do
job_scope = Delayed::Job.where(strand: "canvadocs")
orig_job_count = job_scope.count
attachment = attachment_model(context: @user)
s = @assignment.submit_homework(@user,
submission_type: "online_upload",
attachments: [attachment])
expect(job_scope.count).to eq orig_job_count
end
it "doesn't use canvadocs for moderated grading assignments" do
@assignment.update_attribute :moderated_grading, true
Canvas::Crocodoc.stubs(:enabled?).returns true
Canvadocs.stubs(:enabled?).returns true
Canvadocs.stubs(:annotations_supported?).returns true
attachment = crocodocable_attachment_model(context: @user)
s = @assignment.submit_homework(@user,
submission_type: "online_upload",
attachments: [attachment])
run_jobs
expect(@attachment.canvadoc).to be_nil
expect(@attachment.crocodoc_document).not_to be_nil
end
end
describe "cross-shard attachments" do
specs_require_sharding
it "should work" do
@shard1.activate do
@student = user(:active_user => true)
@attachment = Attachment.create! uploaded_data: StringIO.new('blah'), context: @student, filename: 'blah.txt'
end
course(:active_all => true)
@course.enroll_user(@student, "StudentEnrollment").accept!
@assignment = @course.assignments.create!
sub = @assignment.submit_homework(@user, attachments: [@attachment])
expect(sub.attachments).to eq [@attachment]
end
end
describe '.process_bulk_update' do
before(:once) do
course_with_teacher active_all: true
@u1, @u2 = n_students_in_course(2)
@a1, @a2 = 2.times.map {
@course.assignments.create! points_possible: 10
}
@progress = Progress.create!(context: @course, tag: "submissions_update")
end
it 'updates submissions on an assignment' do
Submission.process_bulk_update(@progress, @course, nil, @teacher, {
@a1.id.to_s => {
@u1.id => {posted_grade: 5},
@u2.id => {posted_grade: 10}
}
})
expect(@a1.submission_for_student(@u1).grade).to eql "5"
expect(@a1.submission_for_student(@u2).grade).to eql "10"
end
it 'updates submissions on multiple assignments' do
Submission.process_bulk_update(@progress, @course, nil, @teacher, {
@a1.id => {
@u1.id => {posted_grade: 5},
@u2.id => {posted_grade: 10}
},
@a2.id.to_s => {
@u1.id => {posted_grade: 10},
@u2.id => {posted_grade: 5}
}
})
expect(@a1.submission_for_student(@u1).grade).to eql "5"
expect(@a1.submission_for_student(@u2).grade).to eql "10"
expect(@a2.submission_for_student(@u1).grade).to eql "10"
expect(@a2.submission_for_student(@u2).grade).to eql "5"
end
it "should maintain grade when only updating comments" do
@a1.grade_student(@u1, :grade => 3)
Submission.process_bulk_update(@progress, @course, nil, @teacher,
{
@a1.id => {
@u1.id => {text_comment: "comment"}
}
})
expect(@a1.submission_for_student(@u1).grade).to eql "3"
end
it "should nil grade when receiving empty posted_grade" do
@a1.grade_student(@u1, :grade => 3)
Submission.process_bulk_update(@progress, @course, nil, @teacher,
{
@a1.id => {
@u1.id => {posted_grade: nil}
}
})
expect(@a1.submission_for_student(@u1).grade).to be_nil
end
end
describe 'find_or_create_provisional_grade!' do
before(:once) do
submission_spec_model
@assignment.moderated_grading = true
@assignment.save!
@teacher2 = User.create(name: "some teacher 2")
@context.enroll_teacher(@teacher2)
end
it "properly creates a provisional grade with all default values but scorer" do
@submission.find_or_create_provisional_grade!(@teacher)
expect(@submission.provisional_grades.length).to eql 1
pg = @submission.provisional_grades.first
expect(pg.scorer_id).to eql @teacher.id
expect(pg.final).to eql false
expect(pg.graded_anonymously).to be_nil
expect(pg.grade).to be_nil
expect(pg.score).to be_nil
expect(pg.source_provisional_grade).to be_nil
end
it "properly amends information to an existing provisional grade" do
@submission.find_or_create_provisional_grade!(@teacher)
@submission.find_or_create_provisional_grade!(@teacher,
score: 15.0,
grade: "20",
graded_anonymously: true
)
expect(@submission.provisional_grades.length).to eql 1
pg = @submission.provisional_grades.first
expect(pg.scorer_id).to eql @teacher.id
expect(pg.final).to eql false
expect(pg.graded_anonymously).to eql true
expect(pg.grade).to eql "20"
expect(pg.score).to eql 15.0
expect(pg.source_provisional_grade).to be_nil
end
it "does not update grade or score if not given" do
@submission.find_or_create_provisional_grade!(@teacher, grade: "20", score: 12.0)
expect(@submission.provisional_grades.first.grade).to eql "20"
expect(@submission.provisional_grades.first.score).to eql 12.0
@submission.find_or_create_provisional_grade!(@teacher)
expect(@submission.provisional_grades.first.grade).to eql "20"
expect(@submission.provisional_grades.first.score).to eql 12.0
end
it "does not update graded_anonymously if not given" do
@submission.find_or_create_provisional_grade!(@teacher, graded_anonymously: true)
expect(@submission.provisional_grades.first.graded_anonymously).to eql true
@submission.find_or_create_provisional_grade!(@teacher)
expect(@submission.provisional_grades.first.graded_anonymously).to eql true
end
it "raises an exception if final is true and user is not allowed to moderate grades" do
expect{ @submission.find_or_create_provisional_grade!(@student, final: true) }
.to raise_error(Assignment::GradeError, "User not authorized to give final provisional grades")
end
it "raises an exception if grade is not final and student does not need a provisional grade" do
@submission.find_or_create_provisional_grade!(@teacher)
expect{ @submission.find_or_create_provisional_grade!(@teacher2, final: false) }
.to raise_error(Assignment::GradeError, "Student already has the maximum number of provisional grades")
end
it "raises an exception if the grade is final and no non-final provisional grades exist" do
expect{ @submission.find_or_create_provisional_grade!(@teacher, final: true) }
.to raise_error(Assignment::GradeError,
"Cannot give a final mark for a student with no other provisional grades")
end
end
describe 'crocodoc_whitelist' do
before(:once) do
submission_spec_model
end
context "not moderated" do
it "returns nil" do
expect(@submission.crocodoc_whitelist).to be_nil
end
end
context "moderated" do
before(:once) do
@assignment.moderated_grading = true
@assignment.save!
@submission.reload
@pg = @submission.find_or_create_provisional_grade!(@teacher, score: 1)
end
context "grades not published" do
context "student not in moderation set" do
it "returns the student alone" do
expect(@submission.crocodoc_whitelist).to eq([@student.reload.crocodoc_id!])
end
end
context "student in moderation set" do
it "returns the student alone" do
@assignment.moderated_grading_selections.create!(student: @student)
expect(@submission.crocodoc_whitelist).to eq([@student.reload.crocodoc_id!])
end
end
end
context "grades published" do
before(:once) do
@assignment.grades_published_at = 1.hour.ago
@assignment.save!
@submission.reload
end
context "student not in moderation set" do
it "returns nil" do
expect(@submission.crocodoc_whitelist).to be_nil
end
end
context "student in moderation set" do
before(:once) do
@sel = @assignment.moderated_grading_selections.create!(student: @student)
end
it "returns nil if no provisional grade was published" do
expect(@submission.crocodoc_whitelist).to be_nil
end
it "returns the student's and selected provisional grader's ids" do
@sel.provisional_grade = @pg
@sel.save!
expect(@submission.crocodoc_whitelist).to match_array([@student.reload.crocodoc_id!,
@teacher.reload.crocodoc_id!])
end
it "returns the student's, provisional grader's, and moderator's ids for a copied mark" do
moderator = @course.enroll_teacher(user_model, :enrollment_state => 'active').user
final = @pg.copy_to_final_mark!(moderator)
@sel.provisional_grade = final
@sel.save!
expect(@submission.crocodoc_whitelist).to match_array([@student.reload.crocodoc_id!,
@teacher.reload.crocodoc_id!,
moderator.reload.crocodoc_id!])
end
end
end
end
end
describe '#rubric_association_with_assessing_user_id' do
before :once do
submission_model assignment: @assignment, user: @student
rubric_association_model association_object: @assignment, purpose: 'grading'
end
subject { @submission.rubric_association_with_assessing_user_id }
it 'sets assessing_user_id to submission.user_id' do
expect(subject.assessing_user_id).to eq @submission.user_id
end
end
describe '#visible_rubric_assessments_for' do
before :once do
submission_model assignment: @assignment, user: @student
@viewing_user = @teacher
end
subject { @submission.visible_rubric_assessments_for(@viewing_user) }
it 'returns empty if assignment is muted?' do
@assignment.update_attribute(:muted, true)
expect(@assignment.muted?).to be_truthy, 'precondition'
expect(subject).to be_empty
end
it 'returns empty if viewing user cannot :read_grade' do
student_in_course(active_all: true)
@viewing_user = @student
expect(@submission.grants_right?(@viewing_user, :read_grade)).to be_falsey, 'precondition'
expect(subject).to be_empty
end
context 'with rubric_assessments' do
before :once do
@assessed_user = @student
rubric_association_model association_object: @assignment, purpose: 'grading'
student_in_course(active_all: true)
[ @teacher, @student ].each do |user|
@rubric_association.rubric_assessments.create!({
artifact: @submission,
assessment_type: 'grading',
assessor: user,
rubric: @rubric,
user: @assessed_user
})
end
@teacher_assessment = @submission.rubric_assessments.where(assessor_id: @teacher).first
@student_assessment = @submission.rubric_assessments.where(assessor_id: @student).first
end
subject { @submission.visible_rubric_assessments_for(@viewing_user) }
it 'returns rubric_assessments for teacher' do
expect(subject).to include(@teacher_assessment)
end
it 'returns only student rubric assessment' do
@viewing_user = @student
expect(subject).not_to include(@teacher_assessment)
expect(subject).to include(@student_assessment)
end
end
end
describe '#add_comment' do
before(:once) do
@submission = Submission.create!(@valid_attributes)
end
it 'creates a draft comment when passed true in the draft_comment option' do
comment = @submission.add_comment(author: @teacher, comment: '42', draft_comment: true)
expect(comment).to be_draft
end
it 'creates a final comment when not passed in a draft_comment option' do
comment = @submission.add_comment(author: @teacher, comment: '42')
expect(comment).not_to be_draft
end
it 'creates a final comment when passed false in the draft_comment option' do
comment = @submission.add_comment(author: @teacher, comment: '42', draft_comment: false)
expect(comment).not_to be_draft
end
end
end
def submission_spec_model(opts={})
@submission = Submission.new(@valid_attributes.merge(opts))
expect(@submission.assignment).to eql(@assignment)
expect(@assignment.context).to eql(@context)
expect(@submission.assignment.context).to eql(@context)
@submission.save!
end