canvas-lms/spec/controllers/gradebooks_controller_spec.rb

561 lines
21 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')
describe GradebooksController do
before :once do
course_with_teacher active_all: true
@teacher_enrollment = @enrollment
student_in_course active_all: true
@student_enrollment = @enrollment
user(:active_all => true)
@observer = @user
@oe = @course.enroll_user(@user, 'ObserverEnrollment')
@oe.accept
@oe.update_attribute(:associated_user_id, @student.id)
end
it "should use GradebooksController" do
expect(controller).to be_an_instance_of(GradebooksController)
end
describe "GET 'index'" do
before(:each) do
Course.expects(:find).returns(['a course'])
end
end
describe "GET 'grade_summary'" do
it "should redirect teacher to gradebook" do
user_session(@teacher)
get 'grade_summary', :course_id => @course.id, :id => nil
expect(response).to redirect_to(:action => 'show')
end
it "should render for current user" do
user_session(@student)
get 'grade_summary', :course_id => @course.id, :id => nil
expect(response).to render_template('grade_summary')
end
it "should render with specified user_id" do
user_session(@student)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(response).to render_template('grade_summary')
expect(assigns[:presenter].courses_with_grades).not_to be_nil
end
it "should not allow access for wrong user" do
user(:active_all => true)
user_session(@user)
get 'grade_summary', :course_id => @course.id, :id => nil
assert_unauthorized
get 'grade_summary', :course_id => @course.id, :id => @student.id
assert_unauthorized
end
it "should allow access for a linked observer" do
user_session(@observer)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(response).to render_template('grade_summary')
expect(assigns[:courses_with_grades]).to be_nil
end
it "should not allow access for a linked student" do
user(:active_all => true)
user_session(@user)
@se = @course.enroll_student(@user)
@se.accept
@se.update_attribute(:associated_user_id, @student.id)
@user.reload
get 'grade_summary', :course_id => @course.id, :id => @student.id
assert_unauthorized
end
it "should not allow access for an observer linked in a different course" do
@course1 = @course
course(:active_all => true)
@course2 = @course
user_session(@observer)
get 'grade_summary', :course_id => @course2.id, :id => @student.id
assert_unauthorized
end
it "should allow concluded teachers to see a student grades pages" do
user_session(@teacher)
@teacher_enrollment.conclude
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(response).to be_success
expect(response).to render_template('grade_summary')
expect(assigns[:courses_with_grades]).to be_nil
end
it "should allow concluded students to see their grades pages" do
user_session(@student)
@student_enrollment.conclude
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(response).to render_template('grade_summary')
end
it "give a student the option to switch between courses" do
pseudonym(@teacher, :username => 'teacher@example.com')
pseudonym(@student, :username => 'student@example.com')
course1 = @course
course2 = course_with_teacher(:user => @teacher, :active_all => 1).course
student_in_course :user => @student, :active_all => 1
user_session(@student)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(response).to be_success
expect(assigns[:presenter].courses_with_grades).not_to be_nil
expect(assigns[:presenter].courses_with_grades.length).to eq 2
end
it "should not give a teacher the option to switch between courses when viewing a student's grades" do
pseudonym(@teacher, :username => 'teacher@example.com')
pseudonym(@student, :username => 'student@example.com')
course1 = @course
course2 = course_with_teacher(:user => @teacher, :active_all => 1).course
student_in_course :user => @student, :active_all => 1
user_session(@teacher)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(response).to be_success
expect(assigns[:courses_with_grades]).to be_nil
end
it "should not give a linked observer the option to switch between courses when viewing a student's grades" do
pseudonym(@teacher, :username => 'teacher@example.com')
pseudonym(@student, :username => 'student@example.com')
observer = user_with_pseudonym(:username => 'parent@example.com', :active_all => 1)
course1 = @course
course2 = course_with_teacher(:user => @teacher, :active_all => 1).course
student_in_course :user => @student, :active_all => 1
oe = course2.enroll_user(@observer, 'ObserverEnrollment')
oe.associated_user = @student
oe.save!
oe.accept
user_session(@observer)
get 'grade_summary', :course_id => course1.id, :id => @student.id
expect(response).to be_success
expect(assigns[:courses_with_grades]).to be_nil
end
it "should assign values for grade calculator to ENV" do
user_session(@teacher)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(assigns[:js_env][:submissions]).not_to be_nil
expect(assigns[:js_env][:assignment_groups]).not_to be_nil
end
it "should not include assignment discussion information in grade calculator ENV data" do
user_session(@teacher)
assignment1 = @course.assignments.create(:title => "Assignment 1")
assignment1.submission_types = "discussion_topic"
assignment1.save!
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(assigns[:js_env][:assignment_groups].first[:assignments].first["discussion_topic"]).to be_nil
end
it "doesn't leak muted scores" do
user_session(@student)
a1, a2 = 2.times.map { |i|
@course.assignments.create! name: "blah#{i}", points_possible: 10
}
a1.mute!
a1.grade_student(@student, grade: 10)
a2.grade_student(@student, grade: 5)
get 'grade_summary', course_id: @course.id, id: @student.id
expected =
expect(assigns[:js_env][:submissions].sort_by { |s|
s['assignment_id']
}).to eq [
{'score' => nil, 'assignment_id' => a1.id},
{'score' => 5, 'assignment_id' => a2.id}
]
end
it "should sort assignments by due date (null last), then title" do
user_session(@teacher)
assignment1 = @course.assignments.create(:title => "Assignment 1")
assignment2 = @course.assignments.create(:title => "Assignment 2", :due_at => 3.days.from_now)
assignment3 = @course.assignments.create(:title => "Assignment 3", :due_at => 2.days.from_now)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(assigns[:presenter].assignments.select{|a| a.class == Assignment}.map(&:id)).to eq [assignment3, assignment2, assignment1].map(&:id)
end
context "with assignment due date overrides" do
before :once do
@assignment = @course.assignments.create(:title => "Assignment 1")
@due_at = 4.days.from_now
end
def check_grades_page(due_at)
[@student, @teacher, @observer].each do |u|
controller.js_env.clear
user_session(u)
get 'grade_summary', :course_id => @course.id, :id => @student.id
expect(assigns[:presenter].assignments.find{|a| a.class == Assignment}.due_at.to_i).to eq due_at.to_i
end
end
it "should reflect section overrides" do
section = @course.default_section
override = assignment_override_model(:assignment => @assignment)
override.set = section
override.override_due_at(@due_at)
override.save!
check_grades_page(@due_at)
end
it "should show the latest section override in student view" do
section = @course.default_section
override = assignment_override_model(:assignment => @assignment)
override.set = section
override.override_due_at(@due_at)
override.save!
section2 = @course.course_sections.create!
override2 = assignment_override_model(:assignment => @assignment)
override2.set = section2
override2.override_due_at(@due_at - 1.day)
override2.save!
user_session(@teacher)
@fake_student = @course.student_view_student
session[:become_user_id] = @fake_student.id
get 'grade_summary', :course_id => @course.id, :id => @fake_student.id
expect(assigns[:presenter].assignments.find{|a| a.class == Assignment}.due_at.to_i).to eq @due_at.to_i
end
it "should reflect group overrides when student is a member" do
@assignment.group_category = group_category
@assignment.save!
group = @assignment.group_category.groups.create!(:context => @course)
group.add_user(@student)
override = assignment_override_model(:assignment => @assignment)
override.set = group
override.override_due_at(@due_at)
override.save!
check_grades_page(@due_at)
end
it "should not reflect group overrides when student is not a member" do
@assignment.group_category = group_category
@assignment.save!
group = @assignment.group_category.groups.create!(:context => @course)
override = assignment_override_model(:assignment => @assignment)
override.set = group
override.override_due_at(@due_at)
override.save!
check_grades_page(nil)
end
it "should reflect ad-hoc overrides" do
override = assignment_override_model(:assignment => @assignment)
override.override_due_at(@due_at)
override.save!
override_student = override.assignment_override_students.build
override_student.user = @student
override_student.save!
check_grades_page(@due_at)
end
it "should use the latest override" do
section = @course.default_section
override = assignment_override_model(:assignment => @assignment)
override.set = section
override.override_due_at(@due_at)
override.save!
override = assignment_override_model(:assignment => @assignment)
override.override_due_at(@due_at + 1.day)
override.save!
override_student = override.assignment_override_students.build
override_student.user = @student
override_student.save!
check_grades_page(@due_at + 1.day)
end
end
it "should raise an exception on a non-integer :id" do
user_session(@teacher)
assert_page_not_found do
get 'grade_summary', :course_id => @course.id, :id => "lqw"
end
end
end
describe "GET 'show'" do
describe "csv" do
before :once do
assignment1 = @course.assignments.create(:title => "Assignment 1")
assignment2 = @course.assignments.create(:title => "Assignment 2")
end
before :each do
user_session(@teacher)
end
shared_examples_for "working download" do
it "should successfully return data" do
get 'show', :course_id => @course.id, :init => 1, :assignments => 1, :format => 'csv'
expect(response).to be_success
expect(response.body).to match(/\AStudent,/)
end
it "should not recompute enrollment grades" do
Enrollment.expects(:recompute_final_score).never
get 'show', :course_id => @course.id, :init => 1, :assignments => 1, :format => 'csv'
end
end
context "with teacher that prefers Grid View" do
before do
@user.preferences[:gradebook_version] = "2"
end
include_examples "working download"
end
context "with teacher that prefers Individual View" do
before do
@user.preferences[:gradebook_version] = "srgb"
end
include_examples "working download"
end
end
context "Individual View" do
before do
user_session(@teacher)
end
it "redirects to Grid View with a friendly URL" do
@teacher.preferences[:gradebook_version] = "2"
get "show", :course_id => @course.id
expect(response).to render_template("gradebook2")
end
it "redirects to Individual View with a friendly URL" do
@teacher.preferences[:gradebook_version] = "srgb"
get "show", :course_id => @course.id
expect(response).to render_template("screenreader")
end
end
it "renders the unauthorized page without gradebook authorization" do
get "show", :course_id => @course.id
expect(response).to render_template("shared/unauthorized")
end
end
describe "GET 'change_gradebook_version'" do
it 'should switch to gradebook2 if clicked' do
user_session(@teacher)
get 'grade_summary', :course_id => @course.id, :id => nil
expect(response).to redirect_to(:action => 'show')
# tell it to use gradebook 2
get 'change_gradebook_version', :course_id => @course.id, :version => 2
expect(response).to redirect_to(:action => 'show')
end
end
describe "POST 'update_submission'" do
it "should allow adding comments for submission" do
user_session(@teacher)
@assignment = @course.assignments.create!(:title => "some assignment")
@student = @course.enroll_user(User.create!(:name => "some user"))
post 'update_submission', :course_id => @course.id, :submission => {:comment => "some comment", :assignment_id => @assignment.id, :user_id => @student.user_id}
expect(response).to be_redirect
expect(assigns[:assignment]).to eql(@assignment)
expect(assigns[:submissions]).not_to be_nil
expect(assigns[:submissions].length).to eql(1)
expect(assigns[:submissions][0].submission_comments).not_to be_nil
expect(assigns[:submissions][0].submission_comments[0].comment).to eql("some comment")
end
it "should allow attaching files to comments for submission" do
user_session(@teacher)
@assignment = @course.assignments.create!(:title => "some assignment")
@student = @course.enroll_user(User.create!(:name => "some user"))
data = fixture_file_upload("scribd_docs/doc.doc", "application/msword", true)
post 'update_submission', :course_id => @course.id, :attachments => {"0" => {:uploaded_data => data}}, :submission => {:comment => "some comment", :assignment_id => @assignment.id, :user_id => @student.user_id}
expect(response).to be_redirect
expect(assigns[:assignment]).to eql(@assignment)
expect(assigns[:submissions]).not_to be_nil
expect(assigns[:submissions].length).to eql(1)
expect(assigns[:submissions][0].submission_comments).not_to be_nil
expect(assigns[:submissions][0].submission_comments[0].comment).to eql("some comment")
expect(assigns[:submissions][0].submission_comments[0].attachments.length).to eql(1)
expect(assigns[:submissions][0].submission_comments[0].attachments[0].display_name).to eql("doc.doc")
end
it "should not allow updating submissions for concluded courses" do
user_session(@teacher)
@teacher_enrollment.complete
@assignment = @course.assignments.create!(:title => "some assignment")
@student = @course.enroll_user(User.create!(:name => "some user"))
post 'update_submission', :course_id => @course.id, :submission => {:comment => "some comment", :assignment_id => @assignment.id, :user_id => @student.user_id}
assert_unauthorized
end
it "should not allow updating submissions in other sections when limited" do
user_session(@teacher)
@teacher_enrollment.update_attribute(:limit_privileges_to_course_section, true)
s1 = submission_model(:course => @course)
s2 = submission_model(:course => @course, :username => 'otherstudent@example.com', :section => @course.course_sections.create(:name => "another section"), :assignment => @assignment)
post 'update_submission', :course_id => @course.id, :submission => {:comment => "some comment", :assignment_id => @assignment.id, :user_id => s1.user_id}
expect(response).to be_redirect
# attempt to grade another section should throw not found
post 'update_submission', :course_id => @course.id, :submission => {:comment => "some comment", :assignment_id => @assignment.id, :user_id => s2.user_id}
expect(flash[:error]).to eql 'Submission was unsuccessful: Submission Failed'
end
end
describe "GET 'speed_grader'" do
it "should redirect user if course's large_roster? setting is true" do
user_session(@teacher)
assignment = @course.assignments.create!(:title => 'some assignment')
Course.any_instance.stubs(:large_roster?).returns(true)
get 'speed_grader', :course_id => @course.id, :assignment_id => assignment.id
expect(response).to be_redirect
expect(flash[:notice]).to eq 'SpeedGrader is disabled for this course'
end
context "draft state" do
before :once do
@assign = @course.assignments.create!(title: 'Totally')
@assign.unpublish
end
before :each do
user_session(@teacher)
end
it "redirects if draft state is enabled and the assignment is unpublished" do
# Unpublished assignment and draft state enabled
get 'speed_grader', course_id: @course, assignment_id: @assign.id
expect(response).to be_redirect
expect(flash[:notice]).to eq I18n.t(
:speedgrader_enabled_only_for_published_content,
'Speedgrader is enabled only for published content.')
# Published assignment and draft state enabled
@assign.publish
get 'speed_grader', course_id: @course, assignment_id: @assign.id
expect(response).not_to be_redirect
end
end
end
describe "POST 'speed_grader_settings'" do
it "lets you set your :enable_speedgrader_grade_by_question preference" do
user_session(@teacher)
expect(@teacher.preferences[:enable_speedgrader_grade_by_question]).not_to be_truthy
post 'speed_grader_settings', course_id: @course.id,
enable_speedgrader_grade_by_question: "1"
expect(@teacher.reload.preferences[:enable_speedgrader_grade_by_question]).to be_truthy
post 'speed_grader_settings', course_id: @course.id,
enable_speedgrader_grade_by_question: "0"
expect(@teacher.reload.preferences[:enable_speedgrader_grade_by_question]).not_to be_truthy
end
end
describe '#light_weight_ags_json' do
it 'should return the necessary JSON for GradeCalculator' do
ag = @course.assignment_groups.create! group_weight: 100
a = ag.assignments.create! :submission_types => 'online_upload',
:points_possible => 10,
:context => @course
AssignmentGroup.add_never_drop_assignment(ag, a)
@controller.instance_variable_set(:@context, @course)
@controller.instance_variable_set(:@current_user, @user)
expect(@controller.light_weight_ags_json([ag])).to eq [
{
id: ag.id,
rules: {
'never_drop' => [
a.id.to_s
]
},
group_weight: 100,
assignments: [
{
id: a.id,
points_possible: 10,
submission_types: ['online_upload'],
}
],
},
]
end
context 'draft state' do
it 'should not return unpublished assignments' do
course_with_student
ag = @course.assignment_groups.create! group_weight: 100
a1 = ag.assignments.create! :submission_types => 'online_upload',
:points_possible => 10,
:context => @course
a2 = ag.assignments.build :submission_types => 'online_upload',
:points_possible => 10,
:context => @course
a2.workflow_state = 'unpublished'
a2.save!
@controller.instance_variable_set(:@context, @course)
@controller.instance_variable_set(:@current_user, @user)
expect(@controller.light_weight_ags_json([ag])).to eq [
{
id: ag.id,
rules: {},
group_weight: 100,
assignments: [
{
id: a1.id,
points_possible: 10,
submission_types: ['online_upload'],
}
],
},
]
end
end
end
end