1490 lines
64 KiB
Ruby
1490 lines
64 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
#
|
|
# Copyright (C) 2011 - present Instructure, Inc.
|
|
#
|
|
# This file is part of Canvas.
|
|
#
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, version 3 of the License.
|
|
#
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
describe SubmissionsController do
|
|
it_behaves_like "a submission update action", :submissions
|
|
it_behaves_like "a submission redo_submission action", :submissions
|
|
|
|
describe "POST create" do
|
|
it "requires authorization" do
|
|
course_with_student(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
assert_unauthorized
|
|
end
|
|
|
|
it "allows submitting homework" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
expect(assigns[:submission].user_id).to eql(@user.id)
|
|
expect(assigns[:submission].assignment_id).to eql(@assignment.id)
|
|
expect(assigns[:submission].submission_type).to eql("online_url")
|
|
expect(assigns[:submission].url).to eql("http://url")
|
|
end
|
|
|
|
it "gracefully handles a submission not yet existing for an assigned student" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
Submission.find_by(assignment: @assignment, user: @student).destroy
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: { submission_type: "online_url", url: "url", user_id: @student.id }
|
|
}
|
|
|
|
expect(response).to be_successful
|
|
end
|
|
|
|
it "only emits one live event" do
|
|
expect(Canvas::LiveEvents).to receive(:submission_created).once
|
|
expect(Canvas::LiveEvents).not_to receive(:submission_updated)
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
end
|
|
|
|
it "does not double-send notifications to a teacher" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@teacher = user_with_pseudonym(username: "teacher@example.com", active_all: 1)
|
|
teacher_in_course(course: @course, user: @teacher, active_all: 1)
|
|
n = Notification.create!(name: "Assignment Submitted", category: "TestImmediately")
|
|
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(@teacher.messages.count).to eq 1
|
|
expect(@teacher.messages.first.notification).to eq n
|
|
end
|
|
|
|
it "allows submitting homework as attachments" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_upload")
|
|
att = attachment_model(context: @user, uploaded_data: stub_file_data("test.txt", "asdf", "text/plain"))
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_upload", attachment_ids: att.id }, attachments: { "0" => { uploaded_data: "" }, "-1" => { uploaded_data: "" } } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
expect(assigns[:submission].user_id).to eql(@user.id)
|
|
expect(assigns[:submission][:assignment_id].to_i).to eql(@assignment.id)
|
|
expect(assigns[:submission][:submission_type]).to eql("online_upload")
|
|
end
|
|
|
|
shared_examples "accepts 'eula_agreement_timestamp' params and persists it in the 'turnitin_data'" do
|
|
let(:submission_type) { raise "set in example" }
|
|
let(:extra_params) { raise "set in example" }
|
|
let(:timestamp) { Time.now.to_i }
|
|
|
|
it "accepts 'eula_agreement_timestamp' params and persists it in the 'turnitin_data'" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: submission_type)
|
|
a1 = attachment_model(context: @user)
|
|
post "create",
|
|
params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: submission_type,
|
|
attachment_ids: a1.id,
|
|
eula_agreement_timestamp: timestamp
|
|
}
|
|
}.merge(extra_params)
|
|
expect(assigns[:submission].turnitin_data[:eula_agreement_timestamp]).to eq timestamp.to_s
|
|
end
|
|
end
|
|
|
|
context "online upload" do
|
|
it_behaves_like "accepts 'eula_agreement_timestamp' params and persists it in the 'turnitin_data'" do
|
|
let(:submission_type) { "online_upload" }
|
|
let(:extra_params) do
|
|
{
|
|
attachments: {
|
|
"0" => { uploaded_data: "" },
|
|
"-1" => { uploaded_data: "" }
|
|
}
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "online text entry" do
|
|
it_behaves_like "accepts 'eula_agreement_timestamp' params and persists it in the 'turnitin_data'" do
|
|
let(:submission_type) { "online_text_entry" }
|
|
let(:extra_params) do
|
|
{
|
|
submission: {
|
|
submission_type: submission_type,
|
|
eula_agreement_timestamp: timestamp,
|
|
body: "body text"
|
|
}
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
context "setting the `resource_link_lookup_uuid` to the submission" do
|
|
let(:tool) do
|
|
Account.default.context_external_tools.create!(
|
|
name: "Undertow",
|
|
url: "http://www.example.com",
|
|
consumer_key: "12345",
|
|
shared_secret: "secret"
|
|
)
|
|
end
|
|
let(:resource_link) do
|
|
Lti::ResourceLink.create(context: @course, context_external_tool: tool, url: "http://www.example.com")
|
|
end
|
|
let(:params) do
|
|
{
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: "online_url",
|
|
}
|
|
}
|
|
end
|
|
|
|
before do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url")
|
|
end
|
|
|
|
it "displays an error when lti resource link is not found" do
|
|
params[:submission][:resource_link_lookup_uuid] = "FOO&BAR"
|
|
|
|
post "create", params: params
|
|
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).to be_nil
|
|
expect(flash[:error]).not_to be_nil
|
|
expect(flash[:error]).to match(/Resource link not found for given `resource_link_lookup_uuid`/)
|
|
end
|
|
|
|
it "creates the submission when lti resource link is found" do
|
|
params[:submission][:resource_link_lookup_uuid] = resource_link.lookup_uuid
|
|
|
|
post "create", params: params
|
|
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
expect(assigns[:submission].resource_link_lookup_uuid).to eql(resource_link.lookup_uuid)
|
|
end
|
|
end
|
|
|
|
it "copies attachments to the submissions folder if that feature is enabled" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_upload")
|
|
att = attachment_model(context: @user, uploaded_data: stub_file_data("test.txt", "asdf", "text/plain"))
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_upload", attachment_ids: att.id }, attachments: { "0" => { uploaded_data: "" }, "-1" => { uploaded_data: "" } } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
att_copy = Attachment.find(assigns[:submission].attachment_ids.to_i)
|
|
expect(att_copy).not_to eq att
|
|
expect(att_copy.root_attachment).to eq att
|
|
expect(att).not_to be_associated_with_submission
|
|
expect(att_copy).to be_associated_with_submission
|
|
end
|
|
|
|
it "rejects illegal file extensions from submission" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "an additional assignment", submission_types: "online_upload", allowed_extensions: ["txt"])
|
|
att = attachment_model(context: @student, uploaded_data: stub_file_data("test.m4v", "asdf", "video/mp4"))
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_upload", attachment_ids: att.id }, attachments: { "0" => { uploaded_data: "" }, "-1" => { uploaded_data: "" } } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).to be_nil
|
|
expect(flash[:error]).not_to be_nil
|
|
expect(flash[:error]).to match(/Invalid file type/)
|
|
end
|
|
|
|
it "uses the appropriate group based on the assignment's category and the current user" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
group_category = @course.group_categories.create(name: "Category")
|
|
@group = @course.groups.create(name: "Group", group_category: group_category)
|
|
@group.add_user(@user)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload", group_category: @group.group_category)
|
|
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:group]).not_to be_nil
|
|
expect(assigns[:group].id).to eql(@group.id)
|
|
end
|
|
|
|
it "does not use a group if the assignment has no category" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
group_category = @course.group_categories.create(name: "Category")
|
|
@group = @course.groups.create(name: "Group", group_category: group_category)
|
|
@group.add_user(@user)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:group]).to be_nil
|
|
end
|
|
|
|
it "allows attaching multiple files to the submission" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
att1 = attachment_model(context: @user, uploaded_data: fixture_file_upload("docs/doc.doc", "application/msword", true))
|
|
att2 = attachment_model(context: @user, uploaded_data: fixture_file_upload("docs/txt.txt", "application/vnd.ms-excel", true))
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id,
|
|
submission: { submission_type: "online_upload", attachment_ids: [att1.id, att2.id].join(",") },
|
|
attachments: { "0" => { uploaded_data: "doc.doc" }, "1" => { uploaded_data: "txt.txt" } } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
expect(assigns[:submission].user_id).to eql(@user.id)
|
|
expect(assigns[:submission].assignment_id).to eql(@assignment.id)
|
|
expect(assigns[:submission].submission_type).to eql("online_upload")
|
|
expect(assigns[:submission].attachments).not_to be_empty
|
|
expect(assigns[:submission].attachments.length).to eql(2)
|
|
expect(assigns[:submission].attachments.map(&:display_name)).to be_include("doc.doc")
|
|
expect(assigns[:submission].attachments.map(&:display_name)).to be_include("txt.txt")
|
|
end
|
|
|
|
it "fails but not raise when the submission is invalid" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url")
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "" } } # blank url not allowed
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).not_to be_nil
|
|
expect(assigns[:submission]).to be_nil
|
|
end
|
|
|
|
it "strips leading/trailing whitespace off url submissions" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url")
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: " http://www.google.com " } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
expect(assigns[:submission].url).to eql("http://www.google.com")
|
|
end
|
|
|
|
it "must accept a basic_lti_launch url when any online submission type is allowed" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url")
|
|
request.path = "/api/v1/courses/#{@course.id}/assignments/#{@assignment.id}/submissions"
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "basic_lti_launch", url: "http://www.google.com" } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).not_to be_nil
|
|
expect(assigns[:submission].submission_type).to eq "basic_lti_launch"
|
|
expect(assigns[:submission].url).to eq "http://www.google.com"
|
|
end
|
|
|
|
it "accepts a basic_lti_launch url for external_tool submission type" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "external_tool")
|
|
request.path = "/api/v1/courses/#{@course.id}/assignments/#{@assignment.id}/submissions"
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "basic_lti_launch", url: "http://www.google.com" } }
|
|
expect(response).to be_redirect
|
|
expect(assigns[:submission]).to_not be_nil
|
|
expect(assigns[:submission].submission_type).to eq "basic_lti_launch"
|
|
expect(assigns[:submission].url).to eq "http://www.google.com"
|
|
end
|
|
|
|
it "prevents submitting non-accepted submission types when not called via the API" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url")
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: { submission_type: "online_text_entry", body: "definitely a URL" }
|
|
}
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).to eq "Assignment does not accept this submission type"
|
|
end
|
|
|
|
it "prevents submitting unpublished submissions from ui" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_text_entry")
|
|
@assignment.update(workflow_state: "unpublished")
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: { submission_type: "online_text_entry", body: "some online text entry" }
|
|
}
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).to eq "You can't submit an assignment when it is unpublished"
|
|
end
|
|
|
|
it "accepts eula agreement timestamp when api submission" do
|
|
timestamp = Time.zone.now.to_i.to_s
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
attachment = attachment_model(context: @student)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_upload")
|
|
request.path = "/api/v1/courses/#{@course.id}/assignments/#{@assignment.id}/submissions"
|
|
params = {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: "online_upload",
|
|
file_ids: [attachment.id],
|
|
eula_agreement_timestamp: timestamp
|
|
}
|
|
}
|
|
post "create", params: params
|
|
expect(assigns[:submission].turnitin_data[:eula_agreement_timestamp]).to eq timestamp
|
|
end
|
|
|
|
it "redirects to the assignment when locked in submit-at-deadline situation" do
|
|
enable_cache do
|
|
now = Time.now.utc
|
|
Timecop.freeze(now) do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_url",
|
|
lock_at: now + 5.seconds
|
|
)
|
|
|
|
# cache permission as true (for 5 minutes)
|
|
expect(@assignment.grants_right?(@student, {}, :submit)).to be_truthy
|
|
end
|
|
|
|
# travel past due date (which resets the Assignment#locked_for? cache)
|
|
Timecop.freeze(now + 10.seconds) do
|
|
# now it's locked, but permission is cached, locked_for? is not
|
|
post "create",
|
|
params: { course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: "online_url",
|
|
url: " http://www.google.com "
|
|
} }
|
|
expect(response).to be_redirect
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "when submitting a text response for the answer" do
|
|
let(:assignment) { @course.assignments.create!(title: "some assignment", submission_types: "online_text_entry") }
|
|
let(:submission_params) { { submission_type: "online_text_entry", body: "My Answer" } }
|
|
|
|
before do
|
|
Setting.set("enable_page_views", "db")
|
|
course_with_student_logged_in active_all: true
|
|
@course.account.enable_service(:avatars)
|
|
post "create", params: { course_id: @course.id, assignment_id: assignment.id, submission: submission_params }
|
|
end
|
|
|
|
after do
|
|
Setting.set("enable_page_views", "false")
|
|
end
|
|
|
|
it "redirects me to the course assignment" do
|
|
expect(response).to be_redirect
|
|
end
|
|
|
|
it "saves a submission object" do
|
|
submission = assigns[:submission]
|
|
expect(submission.id).not_to be_nil
|
|
expect(submission.user_id).to eq @user.id
|
|
expect(submission.body).to eq submission_params[:body]
|
|
end
|
|
|
|
it "logs an asset access for the assignment" do
|
|
accessed_asset = assigns[:accessed_asset]
|
|
expect(accessed_asset[:level]).to eq "submit"
|
|
end
|
|
|
|
it "registers a page view" do
|
|
page_view = assigns[:page_view]
|
|
expect(page_view).not_to be_nil
|
|
expect(page_view.http_method).to eq "post"
|
|
expect(page_view.url).to match %r{^http://test\.host/courses/\d+/assignments/\d+/submissions}
|
|
expect(page_view.participated).to be_truthy
|
|
end
|
|
end
|
|
|
|
it "rewrites canvas content links" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
assignment = @course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_text_entry"
|
|
)
|
|
sub_params = { submission_type: "online_text_entry", body: "<p><img src=\"http://test.host/courses/1/files/1?preview\"></p>" }
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: assignment.id,
|
|
submission: sub_params
|
|
}
|
|
|
|
submission = assigns[:submission]
|
|
expect(submission.body).to eq "<p><img src=\"/courses/1/files/1?preview\"></p>"
|
|
end
|
|
|
|
it "rejects an empty text response" do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
assignment = @course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_text_entry"
|
|
)
|
|
sub_params = { submission_type: "online_text_entry", body: "" }
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: assignment.id,
|
|
submission: sub_params
|
|
}
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).not_to be_nil
|
|
expect(assigns[:submission]).to be_nil
|
|
end
|
|
|
|
context "when the submission_type is student_annotation" do
|
|
before(:once) do
|
|
@course = course_model(workflow_state: "available")
|
|
@attachment = attachment_model(context: @course)
|
|
@assignment = @course.assignments.create!(
|
|
annotatable_attachment: @attachment,
|
|
submission_types: "student_annotation"
|
|
)
|
|
end
|
|
|
|
it "redirects with an error if annotatable_attachment_id is not present in params" do
|
|
course_with_student_logged_in(active_all: true, course: @course)
|
|
post :create, params: {
|
|
assignment_id: @assignment.id,
|
|
course_id: @course.id,
|
|
submission: { submission_type: "student_annotation" }
|
|
}
|
|
|
|
aggregate_failures do
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).to eq "Student Annotation submissions require an annotatable_attachment_id to submit"
|
|
end
|
|
end
|
|
|
|
it "changes a CanvadocsAnnotationContext from draft attempt to the current attempt" do
|
|
course_with_student_logged_in(active_all: true, course: @course)
|
|
submission = @assignment.submissions.find_by(user: @student)
|
|
annotation_context = submission.annotation_context(draft: true)
|
|
|
|
post :create, params: {
|
|
assignment_id: @assignment.id,
|
|
course_id: @course.id,
|
|
submission: { annotatable_attachment_id: @attachment.id, submission_type: "student_annotation" }
|
|
}
|
|
|
|
aggregate_failures do
|
|
annotation_context.reload
|
|
expect(annotation_context.submission_attempt).not_to be_nil
|
|
expect(annotation_context.submission_attempt).to eq submission.reload.attempt
|
|
end
|
|
end
|
|
end
|
|
|
|
context "group comments" do
|
|
before do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@u1 = @user
|
|
student_in_course(course: @course)
|
|
@u2 = @user
|
|
@assignment = @course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_text_entry",
|
|
group_category: GroupCategory.create!(name: "groups", context: @course),
|
|
grade_group_students_individually: false
|
|
)
|
|
@group = @assignment.group_category.groups.create!(name: "g1", context: @course)
|
|
@group.users << @u1
|
|
@group.users << @user
|
|
end
|
|
|
|
it "does not send a comment to the entire group by default" do
|
|
post(
|
|
"create",
|
|
params: { course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: "online_text_entry",
|
|
body: "blah",
|
|
comment: "some comment"
|
|
} }
|
|
)
|
|
|
|
subs = @assignment.submissions
|
|
expect(subs.size).to eq 2
|
|
expect(subs.to_a.sum { |s| s.submission_comments.size }).to eql 1
|
|
end
|
|
|
|
it "does not send a comment to the entire group when false" do
|
|
post(
|
|
"create",
|
|
params: { course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: "online_text_entry",
|
|
body: "blah",
|
|
comment: "some comment",
|
|
group_comment: "0"
|
|
} }
|
|
)
|
|
|
|
subs = @assignment.submissions
|
|
expect(subs.size).to eq 2
|
|
expect(subs.to_a.sum { |s| s.submission_comments.size }).to eql 1
|
|
end
|
|
|
|
it "sends a comment to the entire group if requested" do
|
|
post(
|
|
"create",
|
|
params: { course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: {
|
|
submission_type: "online_text_entry",
|
|
body: "blah",
|
|
comment: "some comment",
|
|
group_comment: "1"
|
|
} }
|
|
)
|
|
|
|
subs = @assignment.submissions
|
|
expect(subs.size).to eq 2
|
|
expect(subs.to_a.sum { |s| s.submission_comments.size }).to eql 2
|
|
end
|
|
|
|
it "succeeds when commenting to the group from a student using PUT" do
|
|
user_session(@u1)
|
|
request.path = "/courses/#{@course.id}/assignments/#{@assignment.id}/submissions/#{@u1.id}"
|
|
post(
|
|
:update,
|
|
params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
id: @u1.id,
|
|
submission: {
|
|
assignment_id: @assignment.id,
|
|
user_id: @u1.id,
|
|
group_comment: "1",
|
|
comment: "some comment"
|
|
},
|
|
},
|
|
format: "json"
|
|
)
|
|
|
|
expect(response).to be_successful
|
|
end
|
|
end
|
|
|
|
context "google doc" do
|
|
before do
|
|
course_with_student_logged_in(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_upload")
|
|
end
|
|
|
|
it "does not save if domain restriction prevents it" do
|
|
allow(@student).to receive(:gmail).and_return("student@does-not-match.com")
|
|
account = Account.default
|
|
flag = FeatureFlag.new
|
|
account.settings[:google_docs_domain] = "example.com"
|
|
account.save!
|
|
flag.context = account
|
|
flag.feature = "google_docs_domain_restriction"
|
|
flag.state = "on"
|
|
flag.save!
|
|
mock_user_service = double
|
|
allow(@user).to receive(:user_services).and_return(mock_user_service)
|
|
expect(mock_user_service).to receive(:where).with(service: "google_drive")
|
|
.and_return(double(first: double(token: "token", secret: "secret")))
|
|
google_docs = double
|
|
expect(GoogleDrive::Connection).to receive(:new).and_return(google_docs)
|
|
|
|
expect(google_docs).to receive(:download).and_return([Net::HTTPOK.new(200, {}, ""), "title", "pdf"])
|
|
post(:create, params: { course_id: @course.id, assignment_id: @assignment.id,
|
|
submission: { submission_type: "google_doc" },
|
|
google_doc: { document_id: "12345" } })
|
|
expect(response).to be_redirect
|
|
end
|
|
|
|
it "uses instfs to save google doc if instfs is enabled" do
|
|
allow(InstFS).to receive(:enabled?).and_return(true)
|
|
uuid = "1234-abcd"
|
|
allow(InstFS).to receive(:direct_upload).and_return(uuid)
|
|
|
|
attachment = @assignment.submissions.first.attachments.new
|
|
SubmissionsController.new.store_google_doc_attachment(attachment, File.open("public/images/a.png"))
|
|
expect(attachment.instfs_uuid).to eq uuid
|
|
end
|
|
|
|
it "gracefully reports a gdrive timeout" do
|
|
mock_user_service = double
|
|
allow(@user).to receive(:user_services).and_return(mock_user_service)
|
|
expect(mock_user_service).to receive(:where).with(service: "google_drive")
|
|
.and_return(double(first: double(token: "token", secret: "secret")))
|
|
google_docs = double
|
|
expect(GoogleDrive::Connection).to receive(:new).and_return(google_docs)
|
|
expect(google_docs).to receive(:download).and_raise(GoogleDrive::ConnectionException, "fake conn timeout")
|
|
post(:create, params: { course_id: @course.id, assignment_id: @assignment.id,
|
|
submission: { submission_type: "google_doc" },
|
|
google_doc: { document_id: "12345" } })
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).to eq("Timed out while talking to google drive")
|
|
end
|
|
|
|
it "gracefully reports an invalid entry" do
|
|
mock_user_service = double
|
|
allow(@user).to receive(:user_services).and_return(mock_user_service)
|
|
expect(mock_user_service).to receive(:where).with(service: "google_drive")
|
|
.and_return(double(first: double(token: "token", secret: "secret")))
|
|
google_docs = double
|
|
expect(GoogleDrive::Connection).to receive(:new).and_return(google_docs)
|
|
expect(google_docs).to receive(:download).and_raise(GoogleDrive::WorkflowError, "fake bad entry")
|
|
post(:create, params: { course_id: @course.id, assignment_id: @assignment.id,
|
|
submission: { submission_type: "google_doc" },
|
|
google_doc: { document_id: "12345" } })
|
|
expect(response).to be_redirect
|
|
expect(flash[:error]).to eq("Google Drive entry was unable to be downloaded")
|
|
end
|
|
end
|
|
|
|
describe "confetti celebrations" do
|
|
before do
|
|
Account.default.enable_feature!(:confetti_for_assignments)
|
|
end
|
|
|
|
context "submission is made before due date" do
|
|
before do
|
|
course_with_student_logged_in(active_all: true)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload", due_at: 5.days.from_now)
|
|
end
|
|
|
|
it "redirects with confetti" do
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(response).to redirect_to(/[?&]confetti=true/)
|
|
end
|
|
|
|
context "confetti_for_assignments flag is disabled" do
|
|
before do
|
|
Account.default.disable_feature!(:confetti_for_assignments)
|
|
end
|
|
|
|
it "redirects without confetti" do
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: { submission_type: "online_url", url: "url" }
|
|
}
|
|
expect(response).to be_redirect
|
|
expect(response).to_not redirect_to(/[?&]confetti=true/)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "submission is made after due date" do
|
|
before do
|
|
course_with_student_logged_in(active_all: true)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload", due_at: 5.days.ago)
|
|
end
|
|
|
|
it "redirects without confetti" do
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(response).to_not redirect_to(/[?&]confetti=true/)
|
|
end
|
|
end
|
|
|
|
context "submission is made with no due date" do
|
|
before do
|
|
course_with_student_logged_in(active_all: true)
|
|
@assignment = @course.assignments.create!(title: "some assignment", submission_types: "online_url,online_upload")
|
|
end
|
|
|
|
it "redirects with confetti" do
|
|
post "create", params: { course_id: @course.id, assignment_id: @assignment.id, submission: { submission_type: "online_url", url: "url" } }
|
|
expect(response).to be_redirect
|
|
expect(response).to redirect_to(/[?&]confetti=true/)
|
|
end
|
|
|
|
context "confetti_for_assignments flag is disabled" do
|
|
before do
|
|
Account.default.disable_feature!(:confetti_for_assignments)
|
|
end
|
|
|
|
it "redirects without confetti" do
|
|
post "create", params: {
|
|
course_id: @course.id,
|
|
assignment_id: @assignment.id,
|
|
submission: { submission_type: "online_url", url: "url" }
|
|
}
|
|
expect(response).to be_redirect
|
|
expect(response).to_not redirect_to(/[?&]confetti=true/)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "tardiness tracker" do
|
|
let(:course) { course_with_student_logged_in(active_all: true) && @course }
|
|
|
|
it "redirects with submitted=0 when assignment has no due date" do
|
|
assignment = course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_url"
|
|
)
|
|
|
|
post "create", params: {
|
|
course_id: course.id,
|
|
assignment_id: assignment.id,
|
|
submission: { submission_type: "online_url", url: "url" }
|
|
}
|
|
|
|
expect(response).to be_redirect
|
|
expect(response).to redirect_to(/[?&]submitted=0/)
|
|
end
|
|
|
|
it "redirects with submitted=1 when submission is made on time" do
|
|
assignment = course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_url",
|
|
due_at: 5.days.from_now
|
|
)
|
|
|
|
post "create", params: {
|
|
course_id: course.id,
|
|
assignment_id: assignment.id,
|
|
submission: { submission_type: "online_url", url: "url" }
|
|
}
|
|
|
|
expect(response).to be_redirect
|
|
expect(response).to redirect_to(/[?&]submitted=1/)
|
|
end
|
|
|
|
it "redirects with submitted=2 when submission is late" do
|
|
assignment = course.assignments.create!(
|
|
title: "some assignment",
|
|
submission_types: "online_url",
|
|
due_at: 1.day.ago
|
|
)
|
|
|
|
post "create", params: {
|
|
course_id: course.id,
|
|
assignment_id: assignment.id,
|
|
submission: { submission_type: "online_url", url: "url" }
|
|
}
|
|
|
|
expect(response).to be_redirect
|
|
expect(response).to redirect_to(/[?&]submitted=2/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET zip" do
|
|
it "zips and download" do
|
|
local_storage!
|
|
course_with_student_and_submitted_homework
|
|
@course.account.enable_service(:avatars)
|
|
|
|
get "index", params: { course_id: @course.id, assignment_id: @assignment.id, zip: "1" }, format: "json"
|
|
expect(response).to be_successful
|
|
|
|
a = Attachment.last
|
|
expect(a.user).to eq @teacher
|
|
expect(a.workflow_state).to eq "to_be_zipped"
|
|
a.update_attribute("workflow_state", "zipped")
|
|
allow_any_instantiation_of(a).to receive("full_filename").and_return(File.expand_path(__FILE__)) # just need a valid file
|
|
allow_any_instantiation_of(a).to receive("content_type").and_return("test/file")
|
|
|
|
request.headers["HTTP_ACCEPT"] = "*/*"
|
|
get "index", params: { course_id: @course.id, assignment_id: @assignment.id, zip: "1" }
|
|
expect(response).to be_successful
|
|
expect(response.media_type).to eq "test/file"
|
|
end
|
|
end
|
|
|
|
describe "GET show" do
|
|
before do
|
|
course_with_student_and_submitted_homework
|
|
@course.account.enable_service(:avatars)
|
|
@context = @course
|
|
@submission.update!(score: 10)
|
|
end
|
|
|
|
let(:body) { JSON.parse(response.body)["submission"] }
|
|
|
|
it "redirects to login when logged out" do
|
|
remove_user_session
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
expect(response).to redirect_to(login_url)
|
|
end
|
|
|
|
it "renders show template" do
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
expect(response).to render_template(:show)
|
|
expect(assigns.dig(:js_env, :media_comment_asset_string)).to eq @teacher.asset_string
|
|
end
|
|
|
|
it "renders json with scores for teachers" do
|
|
request.accept = Mime[:json].to_s
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }, format: :json
|
|
expect(body["id"]).to eq @submission.id
|
|
expect(body["score"]).to eq 10
|
|
expect(body["grade"]).to eq "10"
|
|
expect(body["published_grade"]).to eq "10"
|
|
expect(body["published_score"]).to eq 10
|
|
end
|
|
|
|
it "renders json with scores for students" do
|
|
user_session(@student)
|
|
request.accept = Mime[:json].to_s
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }, format: :json
|
|
expect(body["id"]).to eq @submission.id
|
|
expect(body["score"]).to eq 10
|
|
expect(body["grade"]).to eq "10"
|
|
expect(body["published_grade"]).to eq "10"
|
|
expect(body["published_score"]).to eq 10
|
|
end
|
|
|
|
it "mark read if reading one's own submission" do
|
|
user_session(@student)
|
|
request.accept = Mime[:json].to_s
|
|
@submission.mark_unread(@student)
|
|
@submission.save!
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }, format: :json
|
|
expect(response).to be_successful
|
|
submission = Submission.find(@submission.id)
|
|
expect(submission.read?(@student)).to be_truthy
|
|
end
|
|
|
|
it "don't mark read if reading someone else's submission" do
|
|
user_session(@teacher)
|
|
request.accept = Mime[:json].to_s
|
|
@submission.mark_unread(@student)
|
|
@submission.mark_unread(@teacher)
|
|
@submission.save!
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }, format: :json
|
|
expect(response).to be_successful
|
|
submission = Submission.find(@submission.id)
|
|
expect(submission.read?(@student)).to be_falsey
|
|
expect(submission.read?(@teacher)).to be_falsey
|
|
end
|
|
|
|
it "renders json with scores for teachers for unposted submissions" do
|
|
@assignment.ensure_post_policy(post_manually: true)
|
|
request.accept = Mime[:json].to_s
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }, format: :json
|
|
expect(body["id"]).to eq @submission.id
|
|
expect(body["score"]).to eq 10
|
|
expect(body["grade"]).to eq "10"
|
|
expect(body["published_grade"]).to eq "10"
|
|
expect(body["published_score"]).to eq 10
|
|
end
|
|
|
|
it "renders json without scores for students for unposted submissions" do
|
|
user_session(@student)
|
|
@assignment.ensure_post_policy(post_manually: true)
|
|
request.accept = Mime[:json].to_s
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }, format: :json
|
|
expect(body["id"]).to eq @submission.id
|
|
expect(body["score"]).to be nil
|
|
expect(body["grade"]).to be nil
|
|
expect(body["published_grade"]).to be nil
|
|
expect(body["published_score"]).to be nil
|
|
end
|
|
|
|
it "renders json without scores for students with an unposted submission for a quiz" do
|
|
quiz = @context.quizzes.create!
|
|
quiz.workflow_state = "available"
|
|
quiz.quiz_questions.create!({ question_data: test_quiz_data.first })
|
|
quiz.save!
|
|
quiz.assignment.ensure_post_policy(post_manually: true)
|
|
|
|
quiz_submission = quiz.generate_submission(@student)
|
|
Quizzes::SubmissionGrader.new(quiz_submission).grade_submission
|
|
|
|
user_session(@student)
|
|
request.accept = Mime[:json].to_s
|
|
get :show, params: { course_id: @context.id, assignment_id: quiz.assignment.id, id: @student.id }, format: :json
|
|
expect(body["id"]).to eq quiz_submission.submission.id
|
|
expect(body["body"]).to be nil
|
|
end
|
|
|
|
it "renders the page for submitting student who can access the course" do
|
|
user_session(@student)
|
|
@assignment.update!(anonymous_grading: true)
|
|
@assignment.ensure_post_policy(post_manually: true)
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
assert_status(200)
|
|
expect(assigns.dig(:js_env, :media_comment_asset_string)).to eq @student.asset_string
|
|
end
|
|
|
|
it "does not render the page for submitting student who cannot access the course" do
|
|
user_session(@student)
|
|
@assignment.update!(anonymous_grading: true)
|
|
@assignment.ensure_post_policy(post_manually: true)
|
|
@course.enrollments.find_by(user: @student).deactivate
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
assert_unauthorized
|
|
end
|
|
|
|
describe "peer reviewers" do
|
|
let(:course) { Course.create!(workflow_state: "available") }
|
|
let(:assignment) { course.assignments.create!(peer_reviews: true) }
|
|
let(:reviewer) { course.enroll_user(User.create!, "StudentEnrollment", enrollment_state: "active").user }
|
|
let(:reviewer_sub) { assignment.submissions.find_by!(user: reviewer) }
|
|
let(:student) { course.enroll_user(User.create!, "StudentEnrollment", enrollment_state: "active").user }
|
|
let(:student_sub) { assignment.submissions.find_by!(user: student) }
|
|
|
|
before do
|
|
AssessmentRequest.create!(assessor: reviewer, assessor_asset: reviewer_sub, asset: student_sub, user: student)
|
|
user_session(student)
|
|
end
|
|
|
|
it "renders okay for peer reviewer of student under view" do
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: student.id }
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "renders unauthorized for peer reviewer of a student not under view" do
|
|
new_student = course.enroll_user(User.create!, "StudentEnrollment", enrollment_state: "active").user
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: new_student.id }
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
|
|
context "when anonymous grading is enabled for the assignment" do
|
|
before do
|
|
assignment.update!(anonymous_grading: true)
|
|
end
|
|
|
|
it "renders okay for peer reviewer of student under view" do
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: student.id }
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "renders unauthorized for peer reviewer of a student not under view" do
|
|
new_student = course.enroll_user(User.create!, "StudentEnrollment", enrollment_state: "active").user
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: new_student.id }
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context "when anonymous peer reviews are enabled for the assignment" do
|
|
before do
|
|
assignment.update!(anonymous_peer_reviews: true)
|
|
end
|
|
|
|
it "returns okay when a student attempts to view their own submission" do
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: student.id }
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "returns okay when a teacher attempts to view a student's submission" do
|
|
teacher = course.enroll_teacher(User.create!, enrollment_state: "active").user
|
|
user_session(teacher)
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: student.id }
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "renders unauthorized when a peer reviewer attempts to view the submission under review non-anonymously" do
|
|
user_session(reviewer)
|
|
get :show, params: { course_id: course.id, assignment_id: assignment.id, id: student.id }
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
end
|
|
|
|
it "renders unauthorized for non-submitting student" do
|
|
new_student = User.create!
|
|
@context.enroll_student(new_student, enrollment_state: "active")
|
|
user_session(new_student)
|
|
@assignment.update!(anonymous_grading: true)
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
assert_unauthorized
|
|
end
|
|
|
|
it "renders unauthorized for teacher" do
|
|
user_session(@teacher)
|
|
@assignment.update!(anonymous_grading: true)
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
assert_unauthorized
|
|
end
|
|
|
|
it "renders unauthorized for admin" do
|
|
user_session(account_admin_user)
|
|
@assignment.update!(anonymous_grading: true)
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
assert_unauthorized
|
|
end
|
|
|
|
it "renders the page for site admin" do
|
|
user_session(site_admin_user)
|
|
@assignment.update!(anonymous_grading: true)
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
assert_status(200)
|
|
end
|
|
|
|
context "with user id not present in course" do
|
|
before(:once) do
|
|
course_with_student(active_all: true)
|
|
@course.account.enable_service(:avatars)
|
|
end
|
|
|
|
it "sets flash error" do
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
expect(flash[:error]).not_to be_nil
|
|
end
|
|
|
|
it "redirects to context assignment url" do
|
|
get :show, params: { course_id: @context.id, assignment_id: @assignment.id, id: @student.id }
|
|
expect(response).to redirect_to(course_assignment_url(@context, @assignment))
|
|
end
|
|
end
|
|
|
|
it "shows rubric assessments to peer reviewers" do
|
|
@course.account.enable_service(:avatars)
|
|
@assessor = @student
|
|
outcome_with_rubric
|
|
@association = @rubric.associate_with @assignment, @context, purpose: "grading"
|
|
@assignment.peer_reviews = true
|
|
@assignment.save!
|
|
@assignment.unmute!
|
|
@assignment.assign_peer_review(@assessor, @submission.user)
|
|
@assessment = @association.assess(assessor: @assessor, user: @submission.user, artifact: @submission, assessment: { assessment_type: "grading" })
|
|
user_session(@assessor)
|
|
|
|
get "show", params: { id: @submission.user.id, assignment_id: @assignment.id, course_id: @context.id }
|
|
|
|
expect(response).to be_successful
|
|
|
|
expect(assigns[:visible_rubric_assessments]).to eq [@assessment]
|
|
end
|
|
end
|
|
|
|
context "originality report" do
|
|
let(:test_course) do
|
|
test_course = course_factory(active_course: true)
|
|
test_course.account.enable_service(:avatars)
|
|
test_course.enroll_teacher(test_teacher, enrollment_state: "active")
|
|
test_course.enroll_student(test_student, enrollment_state: "active")
|
|
test_course
|
|
end
|
|
|
|
let(:test_teacher) { User.create }
|
|
let(:test_student) { User.create }
|
|
let(:assignment) { Assignment.create!(title: "test assignment", context: test_course) }
|
|
let(:attachment) { attachment_model(filename: "submission.doc", context: test_student) }
|
|
let(:submission) { assignment.submit_homework(test_student, attachments: [attachment]) }
|
|
let!(:originality_report) do
|
|
OriginalityReport.create!(attachment: attachment,
|
|
submission: submission,
|
|
originality_score: 0.5,
|
|
originality_report_url: "http://www.instructure.com")
|
|
end
|
|
|
|
before do
|
|
user_session(test_teacher)
|
|
end
|
|
|
|
describe "GET originality_report" do
|
|
it "redirects to the originality report URL if it exists" do
|
|
get "originality_report", params: { course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id, asset_string: attachment.asset_string }
|
|
expect(response).to redirect_to originality_report.originality_report_url
|
|
end
|
|
|
|
context "when there are multiple originality reports" do
|
|
let(:submission2) { assignment.submit_homework(test_student, body: "hello world") }
|
|
let(:report_url2) { "http://www.another-test-score.com/" }
|
|
let(:originality_report2) do
|
|
OriginalityReport.create!(attachment: nil,
|
|
submission: submission2,
|
|
originality_score: 0.4,
|
|
originality_report_url: report_url2)
|
|
end
|
|
|
|
it "can use attempt number to find the report url for text entry submissions" do
|
|
originality_report2 # Create immediately
|
|
originality_report.update!(attachment: nil)
|
|
expect(submission2.id).to eq(submission.id) # submission2 is updated/reloaded with new version (last attempt number)
|
|
expect(submission2.attempt).to be > submission.attempt
|
|
get "originality_report", params: {
|
|
course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id,
|
|
asset_string: submission.asset_string, attempt: 1
|
|
}
|
|
expect(response).to redirect_to originality_report.originality_report_url
|
|
get "originality_report", params: {
|
|
course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id,
|
|
asset_string: submission.asset_string, attempt: 2
|
|
}
|
|
expect(response).to redirect_to originality_report2.originality_report_url
|
|
end
|
|
end
|
|
|
|
it "returns bad_request if submission_id is not an integer" do
|
|
get "originality_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: "{ user_id }",
|
|
asset_string: attachment.asset_string
|
|
}
|
|
expect(response).to have_http_status(:bad_request)
|
|
end
|
|
|
|
it "returns unauthorized for users who can't read submission" do
|
|
unauthorized_user = User.create
|
|
user_session(unauthorized_user)
|
|
get "originality_report", params: { course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id, asset_string: attachment.asset_string }
|
|
expect(response.status).to eq 401
|
|
end
|
|
|
|
it "shows an error if no URL is present for the OriginalityReport" do
|
|
originality_report.update_attribute(:originality_report_url, nil)
|
|
get "originality_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: test_student.id,
|
|
asset_string: attachment.asset_string
|
|
}
|
|
expect(flash[:error]).to be_present
|
|
end
|
|
end
|
|
|
|
describe "POST resubmit_to_turnitin" do
|
|
it "returns bad_request if assignment_id is not integer" do
|
|
assignment = assignment_model
|
|
post "resubmit_to_turnitin", params: { course_id: assignment.context_id, assignment_id: "assignment-id", submission_id: test_student.id }
|
|
expect(response).to have_http_status(:bad_request)
|
|
end
|
|
|
|
it "emits a 'plagiarism_resubmit' live event if originality report exists" do
|
|
expect(Canvas::LiveEvents).to receive(:plagiarism_resubmit)
|
|
post "resubmit_to_turnitin", params: { course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id }
|
|
end
|
|
|
|
it "emits a 'plagiarism_resubmit' live event if originality report does not exists" do
|
|
originality_report.destroy
|
|
expect(Canvas::LiveEvents).to receive(:plagiarism_resubmit)
|
|
post "resubmit_to_turnitin", params: { course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id }
|
|
end
|
|
end
|
|
|
|
describe "POST resubmit_to_vericite" do
|
|
it "emits a 'plagiarism_resubmit' live event" do
|
|
expect(Canvas::LiveEvents).to receive(:plagiarism_resubmit)
|
|
post "resubmit_to_vericite", params: { course_id: assignment.context_id, assignment_id: assignment.id, submission_id: test_student.id }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "GET turnitin_report" do
|
|
let(:course) { Course.create! }
|
|
let(:student) { course.enroll_student(User.create!).user }
|
|
let(:teacher) { course.enroll_teacher(User.create!).user }
|
|
let(:assignment) { course.assignments.create!(submission_types: "online_text_entry", title: "hi") }
|
|
let(:submission) { assignment.submit_homework(student, body: "zzzzzzzzzz") }
|
|
let(:asset_string) { submission.id.to_s }
|
|
|
|
before { user_session(teacher) }
|
|
|
|
it "returns bad_request if submission_id is not an integer" do
|
|
get "turnitin_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: "{ user_id }",
|
|
asset_string: asset_string
|
|
}
|
|
expect(response).to have_http_status(:bad_request)
|
|
end
|
|
|
|
context "when the submission's turnitin data contains a report URL" do
|
|
before do
|
|
submission.update!(turnitin_data: { asset_string => { report_url: "MY_GREAT_REPORT" } })
|
|
end
|
|
|
|
it "redirects to the course tool retrieval URL" do
|
|
get "turnitin_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: student.id,
|
|
asset_string: asset_string
|
|
}
|
|
expect(response).to redirect_to(/#{retrieve_course_external_tools_url(course.id)}/)
|
|
end
|
|
|
|
it "includes the report URL in the redirect" do
|
|
get "turnitin_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: student.id,
|
|
asset_string: asset_string
|
|
}
|
|
expect(response).to redirect_to(/MY_GREAT_REPORT/)
|
|
end
|
|
end
|
|
|
|
it "redirects the user to the submission details page if no turnitin URL exists" do
|
|
get "turnitin_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: student.id,
|
|
asset_string: asset_string
|
|
}
|
|
expect(response).to redirect_to course_assignment_submission_url(assignment.context_id, assignment.id, student.id)
|
|
end
|
|
|
|
it "displays a flash error if no turnitin URL exists" do
|
|
get "turnitin_report", params: {
|
|
course_id: assignment.context_id,
|
|
assignment_id: assignment.id,
|
|
submission_id: student.id,
|
|
asset_string: asset_string
|
|
}
|
|
|
|
expect(flash[:error]).to be_present
|
|
end
|
|
end
|
|
|
|
describe "GET audit_events" do
|
|
let(:first_student) { course_with_user("StudentEnrollment", course: @course, name: "First", active_all: true).user }
|
|
|
|
before do
|
|
@course = Course.create!
|
|
@course.account.enable_service(:avatars)
|
|
second_student = course_with_user("StudentEnrollment", course: @course, name: "Second", active_all: true).user
|
|
@teacher = course_with_user("TeacherEnrollment", course: @course, name: "Teacher", active_all: true).user
|
|
@assignment = @course.assignments.create!(name: "anonymous", anonymous_grading: true, updating_user: @teacher)
|
|
@submission = @assignment.submissions.find_by!(user: first_student)
|
|
@submission.submission_comments.create!(author: first_student, comment: "Student comment")
|
|
@submission.submission_comments.create!(author: @teacher, comment: "Teacher comment")
|
|
@unrelated_submission = @assignment.submissions.find_by!(user: second_student)
|
|
@teacher.account.role_overrides.create!(permission: :view_audit_trail, role: teacher_role, enabled: true)
|
|
end
|
|
|
|
before do
|
|
user_session(@teacher)
|
|
end
|
|
|
|
let(:params) do
|
|
{
|
|
assignment_id: @assignment.id,
|
|
course_id: @course.id,
|
|
submission_id: @submission.id
|
|
}
|
|
end
|
|
|
|
it "renders unauthorized if user does not have view_audit_trail permission" do
|
|
@teacher.account.role_overrides.where(permission: :view_audit_trail).destroy_all
|
|
get :audit_events, params: params, format: :json
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
|
|
it "renders ok if user does have view_audit_trail permission" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(response).to have_http_status(:ok)
|
|
end
|
|
|
|
it "returns only related audit events" do
|
|
@unrelated_submission.submission_comments.create!(author: @teacher, comment: "unrelated Teacher comment")
|
|
@course.assignments.create!(name: "unrelated", anonymous_grading: true, updating_user: @teacher)
|
|
get :audit_events, params: params, format: :json
|
|
audit_events = json_parse(response.body).fetch("audit_events")
|
|
expect(audit_events.count).to be 3
|
|
end
|
|
|
|
it "returns the assignment audit events" do
|
|
get :audit_events, params: params, format: :json
|
|
assignment_audit_events = json_parse(response.body).fetch("audit_events").select do |event|
|
|
event.fetch("event_type").include?("assignment_")
|
|
end
|
|
expect(assignment_audit_events.count).to be 1
|
|
end
|
|
|
|
it "returns the submission audit events" do
|
|
get :audit_events, params: params, format: :json
|
|
submission_audit_events = json_parse(response.body).fetch("audit_events").select do |event|
|
|
event.fetch("event_type").include?("submission_")
|
|
end
|
|
expect(submission_audit_events.count).to be 2
|
|
end
|
|
|
|
it "returns the audit events in order of created at" do
|
|
get :audit_events, params: params, format: :json
|
|
audit_event_ids = json_parse(response.body).fetch("audit_events").map do |event|
|
|
event.fetch("id")
|
|
end
|
|
expect(audit_event_ids).to eql audit_event_ids.sort
|
|
end
|
|
|
|
describe "user names and roles" do
|
|
let(:admin) { site_admin_user }
|
|
let(:final_grader) { @teacher }
|
|
let(:other_grader) { User.create!(name: "Nobody") }
|
|
|
|
let(:returned_users) { json_parse(response.body).fetch("users") }
|
|
|
|
before do
|
|
@course.enroll_teacher(other_grader, enrollment_state: "active")
|
|
@assignment.update!(moderated_grading: true, grader_count: 2, final_grader: final_grader)
|
|
|
|
@submission.submission_comments.create!(author: admin, comment: "I am an administrator :)")
|
|
@submission.submission_comments.create!(
|
|
author: other_grader,
|
|
comment: "I am nobody. Who are you? Are you nobody too?"
|
|
)
|
|
end
|
|
|
|
it "returns all users who have generated events for a submission" do
|
|
extraneous_grader = User.create!
|
|
@assignment.create_moderation_grader(extraneous_grader, occupy_slot: true)
|
|
|
|
get :audit_events, params: params, format: :json
|
|
user_ids = returned_users.pluck("id")
|
|
expect(user_ids).to match_array([first_student.id, admin.id, other_grader.id, final_grader.id])
|
|
end
|
|
|
|
it "returns the name associated with a user" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_users).to include(hash_including({ "id" => other_grader.id, "name" => "Nobody" }))
|
|
end
|
|
|
|
it "returns a role of 'final_grader' if a user is the final grader" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_users).to include(hash_including({ "id" => final_grader.id, "role" => "final_grader" }))
|
|
end
|
|
|
|
it "returns a role of 'admin' if a user is an administrator" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_users).to include(hash_including({ "id" => admin.id, "role" => "admin" }))
|
|
end
|
|
|
|
it "returns a role of 'grader' if a user is a grader" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_users).to include(hash_including({ "id" => other_grader.id, "role" => "grader" }))
|
|
end
|
|
|
|
it "returns a role of 'student' if a user is a student" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_users).to include(hash_including({ "id" => first_student.id, "role" => "student" }))
|
|
end
|
|
end
|
|
|
|
describe "external tool events" do
|
|
let(:external_tool) do
|
|
Account.default.context_external_tools.create!(
|
|
name: "Undertow",
|
|
url: "http://www.example.com",
|
|
consumer_key: "12345",
|
|
shared_secret: "secret"
|
|
)
|
|
end
|
|
let(:returned_tools) { json_parse(response.body).fetch("tools") }
|
|
let(:external_tool_events) do
|
|
json_parse(response.body).fetch("audit_events").select do |event|
|
|
event.fetch("event_type").include?("submission_") && event.fetch("context_external_tool_id").present?
|
|
end
|
|
end
|
|
|
|
before { @assignment.grade_student(first_student, grader_id: -external_tool.id, score: 80) }
|
|
|
|
it "returns an event for external tool" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(external_tool_events.count).to be 1
|
|
end
|
|
|
|
it "returns the name associated with an external tool" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_tools).to include(hash_including({ "id" => external_tool.id, "name" => "Undertow" }))
|
|
end
|
|
|
|
it "returns the role of grader for an external tool" do
|
|
get :audit_events, params: params, format: :json
|
|
expect(returned_tools).to include(hash_including({ "id" => external_tool.id, "role" => "grader" }))
|
|
end
|
|
end
|
|
|
|
describe "quiz events" do
|
|
let(:quiz) do
|
|
quiz = @course.quizzes.create!
|
|
quiz.workflow_state = "available"
|
|
quiz.quiz_questions.create!({ question_data: test_quiz_data.first })
|
|
quiz.save!
|
|
quiz.assignment.updating_user = @teacher
|
|
quiz.assignment.update_attribute(:anonymous_grading, true)
|
|
|
|
qsub = Quizzes::SubmissionManager.new(quiz).find_or_create_submission(first_student)
|
|
qsub.quiz_data = test_quiz_data
|
|
qsub.started_at = 1.minute.ago
|
|
qsub.attempt = 1
|
|
qsub.submission_data = [{ points: 0, text: "7051", question_id: 128, correct: false, answer_id: 7051 }]
|
|
qsub.score = 0
|
|
qsub.save!
|
|
qsub.finished_at = Time.now.utc
|
|
qsub.workflow_state = "complete"
|
|
qsub.submission = quiz.assignment.find_or_create_submission(first_student)
|
|
qsub.submission.audit_grade_changes = true
|
|
qsub.with_versioning(true) { qsub.save! }
|
|
|
|
quiz
|
|
end
|
|
let(:quiz_assignment) { quiz.assignment }
|
|
let(:quiz_audit_params) do
|
|
{
|
|
assignment_id: quiz_assignment.id,
|
|
course_id: @course.id,
|
|
submission_id: quiz_assignment.submissions.find_by!(user: first_student).id
|
|
}
|
|
end
|
|
let(:returned_quizzes) { json_parse(response.body).fetch("quizzes") }
|
|
let(:quiz_events) do
|
|
json_parse(response.body).fetch("audit_events").select do |event|
|
|
event.fetch("event_type").include?("submission_") && event.fetch("quiz_id").present?
|
|
end
|
|
end
|
|
|
|
it "returns an event for a quiz" do
|
|
get :audit_events, params: quiz_audit_params, format: :json
|
|
expect(quiz_events.count).to be 1
|
|
end
|
|
|
|
it "returns the name associated with the quiz" do
|
|
get :audit_events, params: quiz_audit_params, format: :json
|
|
expect(returned_quizzes).to include(hash_including({ "id" => quiz.id, "name" => "Unnamed Quiz" }))
|
|
end
|
|
|
|
it "returns the role of grader for a quiz" do
|
|
get :audit_events, params: quiz_audit_params, format: :json
|
|
expect(returned_quizzes).to include(hash_including({ "id" => quiz.id, "role" => "grader" }))
|
|
end
|
|
end
|
|
end
|
|
end
|