1036 lines
42 KiB
Ruby
1036 lines
42 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/>.
|
|
#
|
|
|
|
require_relative "../spec_helper"
|
|
|
|
RSpec.describe SubmissionComment do
|
|
before(:once) do
|
|
course_with_teacher(active_all: true)
|
|
course_with_observer(active_all: true)
|
|
student_in_course(active_all: true)
|
|
|
|
@assignment = @course.assignments.build
|
|
@assignment.workflow_state = :published
|
|
@assignment.save!
|
|
@assignment.unmute!
|
|
|
|
@submission = @assignment.submit_homework(@user)
|
|
end
|
|
|
|
let(:valid_attributes) { { comment: "some comment" } }
|
|
|
|
describe "permissions" do
|
|
describe "delete" do
|
|
context "as a student" do
|
|
it "can delete their own draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student, draft: true)
|
|
expect(comment.grants_right?(@student, :delete)).to be true
|
|
end
|
|
|
|
it "cannot delete their own non-draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student)
|
|
expect(comment.grants_right?(@student, :delete)).to be false
|
|
end
|
|
|
|
it "cannot delete their peers' draft comments" do
|
|
first_student = @student
|
|
student_in_course(active_all: true)
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: first_student, draft: true)
|
|
expect(comment.grants_right?(@student, :delete)).to be false
|
|
end
|
|
|
|
it "cannot delete their peers' non-draft comments" do
|
|
first_student = @student
|
|
student_in_course(active_all: true)
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: first_student)
|
|
expect(comment.grants_right?(@student, :delete)).to be false
|
|
end
|
|
end
|
|
|
|
context "as a teacher" do
|
|
it "can delete their own draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @teacher, draft: true)
|
|
expect(comment.grants_right?(@teacher, :delete)).to be true
|
|
end
|
|
|
|
it "can delete their own non-draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @teacher)
|
|
expect(comment.grants_right?(@teacher, :delete)).to be true
|
|
end
|
|
|
|
it "can delete students' draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student, draft: true)
|
|
expect(comment.grants_right?(@teacher, :delete)).to be true
|
|
end
|
|
|
|
it "can delete students' non-draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student)
|
|
expect(comment.grants_right?(@teacher, :delete)).to be true
|
|
end
|
|
|
|
it "can delete comments in a moderated assignment and the grader is not the final grader" do
|
|
@assignment.update!(moderated_grading: true, grades_published_at: nil, grader_count: 1)
|
|
expect(@teacher.id).not_to eq(@assignment.final_grader_id)
|
|
@submission.grade_posting_in_progress = false
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student)
|
|
expect(comment.grants_right?(@teacher, :delete)).to be true
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "update" do
|
|
context "as a student" do
|
|
it "can update their own draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student, draft: true)
|
|
expect(comment.grants_right?(@student, :update)).to be true
|
|
end
|
|
|
|
it "cannot update their own non-draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student)
|
|
expect(comment.grants_right?(@student, :update)).to be false
|
|
end
|
|
|
|
it "cannot update their peers' draft comments" do
|
|
first_student = @student
|
|
student_in_course(active_all: true)
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: first_student, draft: true)
|
|
expect(comment.grants_right?(@student, :update)).to be false
|
|
end
|
|
|
|
it "cannot update their peers' non-draft comments" do
|
|
first_student = @student
|
|
student_in_course(active_all: true)
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: first_student)
|
|
expect(comment.grants_right?(@student, :update)).to be false
|
|
end
|
|
end
|
|
|
|
context "as a teacher" do
|
|
it "can update their own draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @teacher, draft: true)
|
|
expect(comment.grants_right?(@teacher, :update)).to be true
|
|
end
|
|
|
|
it "can update their own non-draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @teacher)
|
|
expect(comment.grants_right?(@teacher, :update)).to be true
|
|
end
|
|
|
|
it "cannot update students' draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student, draft: true)
|
|
expect(comment.grants_right?(@teacher, :update)).to be false
|
|
end
|
|
|
|
it "cannot update students' non-draft comments" do
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @student)
|
|
expect(comment.grants_right?(@teacher, :update)).to be false
|
|
end
|
|
|
|
it "can update their own comments in a moderated assignment when the grader is not the final grader" do
|
|
@assignment.update!(moderated_grading: true, grades_published_at: nil, grader_count: 1)
|
|
expect(@teacher.id).not_to eq(@assignment.final_grader_id)
|
|
@submission.grade_posting_in_progress = false
|
|
comment = @submission.submission_comments.create!(comment: "hi", author: @teacher)
|
|
expect(comment.grants_right?(@teacher, :update)).to be true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
it "creates a new instance given valid attributes" do
|
|
expect(@submission.submission_comments.create!(valid_attributes)).to be_persisted
|
|
end
|
|
|
|
describe "#set_root_account_id" do
|
|
subject { submission_comment.root_account }
|
|
|
|
let(:submission) { @submission }
|
|
let(:submission_comment) { submission.submission_comments.create!(valid_attributes) }
|
|
|
|
context "as a before_save callback" do
|
|
it { is_expected.to eq submission.context.root_account }
|
|
end
|
|
end
|
|
|
|
describe "#body" do
|
|
it "aliases comment" do
|
|
submission_comment = SubmissionComment.new(comment: "a body")
|
|
expect(submission_comment.body).to eq submission_comment.comment
|
|
end
|
|
end
|
|
|
|
describe "#body=" do
|
|
it "aliases comment=" do
|
|
text = "a body"
|
|
submission_comment = SubmissionComment.new
|
|
submission_comment.body = text
|
|
expect(submission_comment.comment).to eq text
|
|
end
|
|
end
|
|
|
|
describe "viewed submission comments" do
|
|
it "returns read if the submission is read" do
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
@submission.mark_item_read("comment")
|
|
expect(comment).to be_read(@user)
|
|
end
|
|
|
|
it "returns read if there is a viewed submission comment" do
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
comment.viewed_submission_comments.create!(user: @user)
|
|
expect(comment).to be_read(@user)
|
|
end
|
|
|
|
it "creates a viewed submission comment if mark_read! is called" do
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
comment.mark_read!(@user)
|
|
expect(comment).to be_read(@user)
|
|
expect(ViewedSubmissionComment.count).to be(1)
|
|
expect(ViewedSubmissionComment.last.user).to eq(@user)
|
|
expect(ViewedSubmissionComment.last.submission_comment).to eq(comment)
|
|
end
|
|
|
|
it "returns false if the submission is not read and no viewed submission comments" do
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
expect(comment).not_to be_read(@user)
|
|
end
|
|
end
|
|
|
|
describe "notifications" do
|
|
before(:once) do
|
|
@student_ended = user_model
|
|
@section_ended = @course.course_sections.create!(end_at: 1.day.ago)
|
|
|
|
Notification.create!(name: "Submission Comment", category: "TestImmediately")
|
|
Notification.create!(name: "Submission Comment For Teacher")
|
|
end
|
|
|
|
it "dispatches notifications on create for published assignment" do
|
|
comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
expect(comment.messages_sent.keys.sort).to eq ["Submission Comment"]
|
|
|
|
comment = @submission.add_comment(author: @student, comment: "some comment")
|
|
expect(comment.messages_sent.keys.sort).to eq ["Submission Comment For Teacher"]
|
|
end
|
|
|
|
it "dispatches notifications to observers" do
|
|
course_with_observer(active_all: true, active_cc: true, course: @course, associated_user_id: @student.id)
|
|
@submission.add_comment(author: @teacher, comment: "some comment")
|
|
expect(@observer.email_channel.messages.length).to eq 1
|
|
end
|
|
|
|
it "does not send notifications to users in concluded sections" do
|
|
@submission_ended = @assignment.submit_homework(@student_ended)
|
|
@comment = @submission_ended.add_comment(author: @teacher, comment: "some comment")
|
|
expect(@comment.messages_sent.keys).not_to include("Submission Comment")
|
|
end
|
|
|
|
it "does not dispatch notification on create if course is unpublished" do
|
|
@course.complete
|
|
@comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
expect(@course).to_not be_available
|
|
expect(@comment.messages_sent.keys).to_not include("Submission Comment")
|
|
end
|
|
|
|
it "does not dispatch notification on create if student is inactive" do
|
|
@student.enrollments.first.deactivate
|
|
|
|
@comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
expect(@comment.messages_sent.keys).to_not include("Submission Comment")
|
|
end
|
|
|
|
it "does not dispatch notification on create for provisional comments" do
|
|
@comment = @submission.add_comment(author: @teacher, comment: "huttah!", provisional: true)
|
|
expect(@comment.messages_sent).to be_empty
|
|
end
|
|
|
|
it "dispatches notification on create to teachers even if submission not submitted yet" do
|
|
student_in_course(active_all: true)
|
|
@submission = @assignment.find_or_create_submission(@student)
|
|
@comment = @submission.add_comment(author: @student, comment: "some comment")
|
|
expect(@submission).to be_unsubmitted
|
|
expect(@comment.messages_sent).to include("Submission Comment For Teacher")
|
|
end
|
|
|
|
it "doesn't dispatch notifications on create for manually posted assignments" do
|
|
@assignment.ensure_post_policy(post_manually: true)
|
|
@assignment.hide_submissions(submission_ids: [@submission.id])
|
|
|
|
@comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
expect(@comment.messages_sent.keys).not_to include("Submission Comment")
|
|
end
|
|
|
|
context "draft comment" do
|
|
before do
|
|
@comment = @submission.add_comment(author: @teacher, comment: "42", draft_comment: true)
|
|
end
|
|
|
|
it "does not dispatch notification on create" do
|
|
expect(@comment.messages_sent).to be_empty
|
|
end
|
|
|
|
it "dispatches notification on update when the draft changes to false" do
|
|
@comment.draft = false
|
|
@comment.save
|
|
|
|
expect(@comment.messages_sent.keys).to eq(["Submission Comment"])
|
|
end
|
|
end
|
|
end
|
|
|
|
it "allows valid attachments" do
|
|
a = Attachment.create!(context: @assignment, uploaded_data: default_uploaded_data)
|
|
@comment = @submission.submission_comments.create!(valid_attributes)
|
|
expect(a.recently_created).to be(true)
|
|
@comment.reload
|
|
@comment.update(attachments: [a])
|
|
expect(@comment.attachment_ids).to eql(a.id.to_s)
|
|
expect(@comment.cached_attachments.first).to be_an(Attachment)
|
|
expect(@comment.cached_attachments).to eql [a]
|
|
end
|
|
|
|
it "rejects invalid attachments" do
|
|
a = Attachment.create!(context: @assignment, uploaded_data: default_uploaded_data)
|
|
a.recently_created = false
|
|
@comment = @submission.submission_comments.create!(valid_attributes)
|
|
@comment.update(attachments: [a])
|
|
expect(@comment.attachment_ids).to eql("")
|
|
end
|
|
|
|
it "handles legacy OpenObject attachments" do
|
|
a = Attachment.create!(context: @assignment, uploaded_data: default_uploaded_data)
|
|
a.recently_created = false
|
|
@comment = @submission.submission_comments.create!(valid_attributes)
|
|
@comment.update(attachments: [a])
|
|
@comment.cached_attachments = [OpenObject.new(a.attributes, in_specs: true)]
|
|
expect(@comment.cached_attachments.first).to be_an(Attachment)
|
|
expect(@comment.cached_attachments).to eql [a]
|
|
end
|
|
|
|
it "handles even older legacy OpenObject attachments" do
|
|
a = Attachment.create!(context: @assignment, uploaded_data: default_uploaded_data)
|
|
a.recently_created = false
|
|
@comment = @submission.submission_comments.create!(valid_attributes)
|
|
@comment.update(attachments: [a])
|
|
@comment.cached_attachments = [OpenObject.new({ table: a.attributes, object_type: "attachment" }, in_specs: true)]
|
|
expect(@comment.cached_attachments.first).to be_an(Attachment)
|
|
expect(@comment.cached_attachments).to eql [a]
|
|
end
|
|
|
|
it "renders formatted_body correctly" do
|
|
@comment = @submission.submission_comments.create!(valid_attributes)
|
|
@comment.comment = <<~TEXT
|
|
This text has a http://www.google.com link in it...
|
|
|
|
> and some
|
|
> quoted text
|
|
TEXT
|
|
@comment.save!
|
|
body = @comment.formatted_body
|
|
expect(body).to match(/<a/)
|
|
expect(body).to match(/quoted_text/)
|
|
end
|
|
|
|
def prepare_test_submission
|
|
assignment_model
|
|
@assignment.workflow_state = "published"
|
|
@assignment.save
|
|
@course.offer
|
|
@course.enroll_teacher(user_factory)
|
|
@se = @course.enroll_student(user_factory)
|
|
@assignment.reload
|
|
@submission = @assignment.submit_homework(@se.user, body: "some message")
|
|
@submission.created_at = Time.now - 60
|
|
@submission.save
|
|
end
|
|
|
|
it "sends the submission to the stream" do
|
|
prepare_test_submission
|
|
@comment = @submission.add_comment(author: @se.user, comment: "some comment")
|
|
@item = StreamItem.last
|
|
expect(@item).not_to be_nil
|
|
expect(@item.asset).to eq @submission
|
|
expect(@item.data).to be_is_a(Submission)
|
|
expect(@item.data.submission_comments.target).to eq [] # not stored on the stream item
|
|
expect(@item.data.submission_comments).to eq [@comment] # but we can still get them
|
|
expect(@item.stream_item_instances.first.read?).to be_truthy
|
|
end
|
|
|
|
it "marks last_comment_at on the submission" do
|
|
prepare_test_submission
|
|
@submission.add_comment(author: @submission.user, comment: "some comment")
|
|
expect(@submission.reload.last_comment_at).to be_nil
|
|
|
|
draft_comment = @submission.add_comment(author: @teacher, comment: "some comment", draft_comment: true)
|
|
expect(@submission.reload.last_comment_at).to be_nil
|
|
|
|
frd_comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
expect(@submission.reload.last_comment_at.to_i).to eq frd_comment.created_at.to_i
|
|
|
|
draft_comment.update(draft: false, created_at: 2.days.from_now) # should re-run after update
|
|
expect(@submission.reload.last_comment_at.to_i).to eq draft_comment.created_at.to_i
|
|
|
|
draft_comment.destroy # should re-run after destroy
|
|
expect(@submission.reload.last_comment_at.to_i).to eq frd_comment.created_at.to_i
|
|
end
|
|
|
|
it "does not create a stream item for a provisional comment" do
|
|
prepare_test_submission
|
|
expect do
|
|
@submission.add_comment(author: @teacher, comment: "some comment", provisional: true)
|
|
end.not_to change(StreamItem, :count)
|
|
end
|
|
|
|
it "ensures the media object exists" do
|
|
assignment_model
|
|
se = @course.enroll_student(user_factory)
|
|
@submission = @assignment.submit_homework(se.user, body: "some message")
|
|
expect(MediaObject).to receive(:ensure_media_object).with("fake", { context: se.user, user: se.user })
|
|
@comment = @submission.add_comment(author: se.user, media_comment_type: "audio", media_comment_id: "fake")
|
|
end
|
|
|
|
describe "peer reviews" do
|
|
before(:once) do
|
|
@student1 = @student
|
|
@student2 = student_in_course(active_all: true).user
|
|
@student3 = student_in_course(active_all: true).user
|
|
|
|
@assignment.peer_reviews = true
|
|
@assignment.save!
|
|
@assignment.assign_peer_review(@student2, @student1)
|
|
@assignment.assign_peer_review(@student3, @student1)
|
|
end
|
|
|
|
it "prevents peer reviewer from seeing other comments" do
|
|
@teacher_comment = @submission.add_comment(author: @teacher, comment: "some comment from teacher")
|
|
@reviewer_comment = @submission.add_comment(author: @student2, comment: "some comment from peer reviewer")
|
|
@my_comment = @submission.add_comment(author: @student3, comment: "some comment from me")
|
|
|
|
expect(@teacher_comment.grants_right?(@student3, :read)).to be_falsey
|
|
expect(@reviewer_comment.grants_right?(@student3, :read)).to be_falsey
|
|
expect(@my_comment.grants_right?(@student3, :read)).to be_truthy
|
|
|
|
expect(@teacher_comment.grants_right?(@student1, :read)).to be_truthy
|
|
expect(@reviewer_comment.grants_right?(@student1, :read)).to be_truthy
|
|
expect(@my_comment.grants_right?(@student1, :read)).to be_truthy
|
|
end
|
|
|
|
describe "when anonymous" do
|
|
before(:once) do
|
|
@assignment.update_attribute(:anonymous_peer_reviews, true)
|
|
@reviewer_comment = @submission.add_comment({
|
|
author: @student2,
|
|
comment: "My peer review comment."
|
|
})
|
|
|
|
@teacher_comment = @submission.add_comment({
|
|
author: @teacher,
|
|
comment: "My teacher review comment."
|
|
})
|
|
end
|
|
|
|
it "marks submission comment as anonymous" do
|
|
expect(@reviewer_comment.anonymous?).to be_truthy
|
|
end
|
|
|
|
it "prevents reviewed from seeing reviewer name" do
|
|
expect(@reviewer_comment.grants_right?(@student1, :read_author)).to be_falsey
|
|
end
|
|
|
|
it "allows teacher to see reviewer name" do
|
|
expect(@reviewer_comment.grants_right?(@teacher, :read_author)).to be_truthy
|
|
end
|
|
|
|
it "allows reviewed to see a teacher comment" do
|
|
expect(@teacher_comment.grants_right?(@student1, :read_author)).to be_truthy
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "reply_from" do
|
|
it "ignores replies on deleted accounts" do
|
|
comment = @submission.add_comment(user: @teacher, comment: "some comment")
|
|
Account.default.destroy
|
|
comment.reload
|
|
expect do
|
|
comment.reply_from(user: @student, text: "some reply")
|
|
end.to raise_error(IncomingMail::Errors::UnknownAddress)
|
|
end
|
|
|
|
it "creates reply" do
|
|
comment = @submission.add_comment(user: @teacher, comment: "blah")
|
|
reply = comment.reply_from(user: @teacher, text: "oops I meant blah")
|
|
expect(reply.provisional_grade).to be_nil
|
|
end
|
|
|
|
it "does not create reply for observers" do
|
|
comment = @submission.add_comment(user: @teacher, comment: "blah")
|
|
expect do
|
|
comment.reply_from(user: @observer, text: "some reply")
|
|
end.to raise_error(IncomingMail::Errors::InvalidParticipant)
|
|
end
|
|
|
|
it "creates reply in the same provisional grade" do
|
|
comment = @submission.add_comment(user: @teacher, comment: "blah", provisional: true)
|
|
reply = comment.reply_from(user: @teacher, text: "oops I meant blah")
|
|
expect(reply.provisional_grade).to eq(comment.provisional_grade)
|
|
expect(reply.provisional_grade.scorer).to eq @teacher
|
|
end
|
|
|
|
it "posts submissions for auto-posted assignments" do
|
|
assignment = @course.assignments.create!
|
|
submission = assignment.submission_for_student(@student)
|
|
comment = submission.add_comment(user: @student, comment: "student")
|
|
expect do
|
|
comment.reply_from(user: @teacher, text: "teacher")
|
|
end.to change {
|
|
submission.reload.posted?
|
|
}.from(false).to(true)
|
|
end
|
|
end
|
|
|
|
describe "read/unread state" do
|
|
it "is unread after submission is commented on by teacher" do
|
|
expect do
|
|
@comment = @submission.submission_comments.create!(valid_attributes.merge({ author: @teacher }))
|
|
end.to change(ContentParticipation, :count).by(1)
|
|
|
|
expect(ContentParticipation.where(user_id: @student).first).to be_unread
|
|
expect(@submission.unread?(@student)).to be_truthy
|
|
end
|
|
|
|
it "is read after submission is commented on by self" do
|
|
expect do
|
|
@comment = @submission.submission_comments.create!(valid_attributes.merge({ author: @student }))
|
|
end.not_to change(ContentParticipation, :count)
|
|
|
|
expect(@submission.read?(@student)).to be_truthy
|
|
end
|
|
|
|
it "does not set unread state when a provisional comment is made" do
|
|
expect do
|
|
@submission.add_comment(author: @teacher, comment: "wat", provisional: true)
|
|
end.not_to change(ContentParticipation, :count)
|
|
|
|
expect(@submission.read?(@student)).to be true
|
|
end
|
|
|
|
it "is unread when at least a comment is not commented by self" do
|
|
expect do
|
|
@submission.submission_comments.create!(valid_attributes.merge({ author: @student }))
|
|
@submission.submission_comments.create!(valid_attributes.merge({ author: @teacher }))
|
|
end.to change(ContentParticipation, :count).by(1)
|
|
|
|
expect(@submission.unread?(@student)).to be_truthy
|
|
end
|
|
end
|
|
|
|
describe "after_destroy #delete_other_comments_in_this_group" do
|
|
context "given a submission with several group comments" do
|
|
let!(:assignment) { @course.assignments.create! }
|
|
let!(:unrelated_assignment) { @course.assignments.create! }
|
|
let!(:submission) { assignment.submissions.find_by!(user: @user) }
|
|
let!(:unrelated_submission) { unrelated_assignment.submissions.find_by!(user: @user) }
|
|
let!(:first_comment) do
|
|
submission.submission_comments.create!(
|
|
group_comment_id: "uuid",
|
|
comment: "first comment"
|
|
)
|
|
end
|
|
let!(:second_comment) do
|
|
submission.submission_comments.create!(
|
|
group_comment_id: "uuid",
|
|
comment: "second comment"
|
|
)
|
|
end
|
|
let!(:ungrouped_comment) do
|
|
submission.submission_comments.create!(
|
|
comment: "third comment (ungrouped)"
|
|
)
|
|
end
|
|
let!(:unrelated_comment) do
|
|
unrelated_submission.submission_comments.create!(
|
|
comment: "unrelated: first comment"
|
|
)
|
|
end
|
|
let!(:unrelated_group_comment) do
|
|
unrelated_submission.submission_comments.create!(
|
|
group_comment_id: "uuid",
|
|
comment: "unrelated: second comment (grouped)"
|
|
)
|
|
end
|
|
|
|
it "deletes other group comments on destroy" do
|
|
expect do
|
|
first_comment.destroy
|
|
end.to change { submission.submission_comments.count }.from(3).to(1)
|
|
expect(submission.submission_comments.reload).not_to include first_comment, second_comment
|
|
expect(submission.submission_comments.reload).to include ungrouped_comment
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "after_update #publish_other_comments_in_this_group" do
|
|
context "given a submission with several group comments" do
|
|
let!(:assignment) { @course.assignments.create! }
|
|
let!(:unrelated_assignment) { @course.assignments.create! }
|
|
let!(:submission) { assignment.submissions.find_by!(user: @user) }
|
|
let!(:unrelated_submission) { unrelated_assignment.submissions.find_by!(user: @user) }
|
|
let!(:first_comment) do
|
|
submission.submission_comments.create!(
|
|
group_comment_id: "uuid",
|
|
comment: "first comment",
|
|
draft: true
|
|
)
|
|
end
|
|
let!(:second_comment) do
|
|
submission.submission_comments.create!(
|
|
group_comment_id: "uuid",
|
|
comment: "second comment",
|
|
draft: true
|
|
)
|
|
end
|
|
let!(:ungrouped_comment) do
|
|
submission.submission_comments.create!(
|
|
comment: "third comment (ungrouped)",
|
|
draft: true
|
|
)
|
|
end
|
|
let!(:unrelated_comment) do
|
|
unrelated_submission.submission_comments.create!(
|
|
comment: "unrelated: first comment",
|
|
draft: true
|
|
)
|
|
end
|
|
let!(:unrelated_group_comment) do
|
|
unrelated_submission.submission_comments.create!(
|
|
group_comment_id: "uuid",
|
|
comment: "unrelated: second comment (grouped)",
|
|
draft: true
|
|
)
|
|
end
|
|
|
|
it "updates other group comments when published" do
|
|
expect do
|
|
first_comment.update_attribute(:draft, false)
|
|
end.to change { SubmissionComment.published.count }.from(0).to(2)
|
|
expect(submission.submission_comments.published.pluck(:id)).to include first_comment.id, second_comment.id
|
|
expect(submission.submission_comments.published.pluck(:id)).not_to include ungrouped_comment.id
|
|
end
|
|
end
|
|
end
|
|
|
|
context "given group and nongroup comments" do
|
|
before(:once) do
|
|
@group_comment = @submission.submission_comments.create!(group_comment_id: "foo")
|
|
@nongroup_comment = @submission.submission_comments.create!
|
|
end
|
|
|
|
describe "scope: for_groups" do
|
|
subject { SubmissionComment.for_groups }
|
|
|
|
it { is_expected.to include(@group_comment) }
|
|
it { is_expected.not_to include(@nongroup_comment) }
|
|
end
|
|
|
|
describe "scope: not_for_groups" do
|
|
subject { SubmissionComment.not_for_groups }
|
|
|
|
it { is_expected.not_to include(@group_comment) }
|
|
it { is_expected.to include(@nongroup_comment) }
|
|
end
|
|
end
|
|
|
|
describe "scope: draft" do
|
|
before(:once) do
|
|
@standard_comment = @submission.submission_comments.create!(valid_attributes)
|
|
@published_comment = @submission.submission_comments.create!(valid_attributes.merge({ draft: false }))
|
|
@draft_comment = @submission.submission_comments.create!(valid_attributes.merge({ draft: true }))
|
|
end
|
|
|
|
it "returns the draft comment" do
|
|
expect(SubmissionComment.draft.pluck(:id)).to include(@draft_comment.id)
|
|
end
|
|
|
|
it "does not return the standard comment" do
|
|
expect(SubmissionComment.draft.pluck(:id)).not_to include(@standard_comment.id)
|
|
end
|
|
|
|
it "does not return the published comment" do
|
|
expect(SubmissionComment.draft.pluck(:id)).not_to include(@published_comment.id)
|
|
end
|
|
end
|
|
|
|
describe "scope: published" do
|
|
before(:once) do
|
|
@published_comment = @submission.submission_comments.create!(valid_attributes.merge({ draft: false }))
|
|
@draft_comment = @submission.submission_comments.create!(valid_attributes.merge({ draft: true }))
|
|
end
|
|
|
|
it "does not return the draft comment" do
|
|
expect(SubmissionComment.published.pluck(:id)).not_to include(@draft_comment.id)
|
|
end
|
|
|
|
it "returns the published comment" do
|
|
expect(SubmissionComment.published.pluck(:id)).to include(@published_comment.id)
|
|
end
|
|
end
|
|
|
|
describe "authorization policy" do
|
|
context "draft comment" do
|
|
before(:once) do
|
|
course_with_user("TeacherEnrollment", course: @course)
|
|
@second_teacher = @user
|
|
|
|
@submission_comment = @submission.submission_comments.create!(valid_attributes.merge({
|
|
draft: true,
|
|
author: @teacher
|
|
}))
|
|
end
|
|
|
|
it "can be updated by the teacher who created it" do
|
|
expect(@submission_comment).to be_grants_any_right(@teacher, :update)
|
|
end
|
|
|
|
it "cannot be updated by a different teacher on the same course" do
|
|
expect(@submission_comment).not_to be_grants_any_right(@second_teacher, :update)
|
|
end
|
|
|
|
it "cannot be read by a student if it would otherwise be readable by them" do
|
|
@submission_comment.teacher_only_comment = false
|
|
|
|
expect(@submission_comment).not_to be_grants_any_right(@student, :read)
|
|
end
|
|
|
|
it "cannot be read by an observer of the receiving student if it would otherwise be readable by the student" do
|
|
@submission_comment.teacher_only_comment = false
|
|
|
|
observer = User.create!
|
|
@course.enroll_user(observer, "ObserverEnrollment", enrollment_state: :active, associated_user_id: @student.id)
|
|
expect(@submission_comment).not_to be_grants_any_right(observer, :read)
|
|
end
|
|
end
|
|
|
|
describe "viewing comments" do
|
|
context "when the assignment is not moderated" do
|
|
let(:course) { Course.create! }
|
|
let(:assignment) { course.assignments.create!(title: "hi") }
|
|
let(:ta) { course.enroll_ta(User.create!, active_all: true).user }
|
|
let(:student) { course.enroll_student(User.create!, enrollment_state: "active").user }
|
|
let(:submission) { assignment.submission_for_student(student) }
|
|
let(:comment) do
|
|
assignment.update_submission(student, commenter: student, comment: "ok")
|
|
submission.submission_comments.first
|
|
end
|
|
|
|
it "submitter comments can be read by an instructor with default permissions" do
|
|
expect(comment.grants_right?(ta, :read)).to be true
|
|
end
|
|
|
|
it "submitter comments can be read by an instructor who cannot manage assignments but can view the submitter's grades" do
|
|
RoleOverride.create!(context: course.account, permission: :manage_assignments, role: ta_role, enabled: false)
|
|
expect(comment.grants_right?(ta, :read)).to be true
|
|
end
|
|
|
|
it "does not allow author to be read if current_user is not present" do
|
|
expect(comment.grants_right?(nil, :read_author)).to be false
|
|
end
|
|
|
|
describe "anonymous assignments" do
|
|
let(:assignment) { course.assignments.create!(title: "hi", anonymous_grading: true) }
|
|
|
|
it "allows students to read the author of their own comments" do
|
|
expect(comment.grants_right?(student, :read_author)).to be true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#update_submission" do
|
|
context "draft comment" do
|
|
before(:once) do
|
|
@submission.submission_comments.create!(valid_attributes)
|
|
@submission_comment = @submission.submission_comments.create!(valid_attributes.merge({
|
|
draft: true,
|
|
author: @teacher
|
|
}))
|
|
end
|
|
|
|
it "is not reflected in the submission's submission_comments_count" do
|
|
expect(@submission_comment.submission.reload.submission_comments_count).to eq(1)
|
|
end
|
|
|
|
it "is reflected in the submission's submission_comments_count as soon as its draft field changes" do
|
|
@submission_comment.draft = false
|
|
|
|
expect { @submission_comment.save }.to(
|
|
change { @submission_comment.submission.reload.submission_comments_count }
|
|
.from(1).to(2)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#auditable?" do
|
|
it "is auditable if it is not a draft and the assignment is auditable" do
|
|
@assignment.update!(anonymous_grading: true)
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
expect(comment).to be_auditable
|
|
end
|
|
|
|
it "is not auditable if it is a draft and the assignment is auditable" do
|
|
@assignment.update!(anonymous_grading: true)
|
|
comment = @submission.submission_comments.create!(valid_attributes.merge(draft: true))
|
|
expect(comment).not_to be_auditable
|
|
end
|
|
|
|
it "is not auditable if it is not a draft and the assignment is not auditable" do
|
|
@assignment.update!(anonymous_grading: false, moderated_grading: false)
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
expect(comment).not_to be_auditable
|
|
end
|
|
|
|
it "is not auditable if posting grades" do
|
|
@assignment.update!(anonymous_grading: true)
|
|
comment = @submission.submission_comments.create!(valid_attributes)
|
|
comment.grade_posting_in_progress = true
|
|
expect(comment).not_to be_auditable
|
|
end
|
|
end
|
|
|
|
describe "#edited_at" do
|
|
before(:once) do
|
|
@comment = @submission.submission_comments.create!(valid_attributes)
|
|
end
|
|
|
|
it "is nil for newly-created submission comments" do
|
|
expect(@comment.edited_at).to be_nil
|
|
end
|
|
|
|
it "remains nil if the submission comment is updated but the 'comment' attribute is unchanged" do
|
|
@comment.update!(draft: true, hidden: true)
|
|
expect(@comment.edited_at).to be_nil
|
|
end
|
|
|
|
it "is set if the 'comment' attribute is updated on the submission comment" do
|
|
now = Time.zone.now
|
|
Timecop.freeze(now) { @comment.update!(comment: "changing the comment!") }
|
|
expect(@comment.edited_at).to eql now
|
|
end
|
|
|
|
it "is updated on subsequent changes to the 'comment' attribute" do
|
|
now = Time.zone.now
|
|
Timecop.freeze(now) { @comment.update!(comment: "changing the comment!") }
|
|
|
|
later = 2.minutes.from_now(now)
|
|
Timecop.freeze(later) do
|
|
expect { @comment.update!(comment: "and again, changing it!") }.to change {
|
|
@comment.edited_at
|
|
}.from(now).to(later)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "audit event logging" do
|
|
before(:once) { @assignment.update!(anonymous_grading: true, grader_count: 2) }
|
|
|
|
it "creates exactly one AnonymousOrModerationEvent on creation" do
|
|
expect { @submission.submission_comments.create!(author: @student, anonymous: false) }
|
|
.to change { AnonymousOrModerationEvent.count }.by(1)
|
|
end
|
|
|
|
it "on creation of the comment, the payload of the event includes boolean values that were set to false" do
|
|
@submission.submission_comments.create!(author: @student, anonymous: false)
|
|
payload = AnonymousOrModerationEvent.where(assignment: @assignment).last.payload
|
|
expect(payload).to include("anonymous" => false)
|
|
end
|
|
|
|
it "does not create an event on creation when no author present" do
|
|
expect do
|
|
@submission.submission_comments.create!(comment: "a comment")
|
|
end.not_to change { AnonymousOrModerationEvent.count }
|
|
end
|
|
|
|
it "does not create an event when no updating_user present" do
|
|
comment = @submission.submission_comments.create!(author: @student)
|
|
expect { comment.update!(comment: "changing the comment!") }.not_to change { AnonymousOrModerationEvent.count }
|
|
end
|
|
end
|
|
|
|
describe "#attempt" do
|
|
before(:once) do
|
|
@submission.update!(attempt: 4)
|
|
@comment1 = @submission.submission_comments.create!(valid_attributes.merge(attempt: 1))
|
|
@comment2 = @submission.submission_comments.create!(valid_attributes.merge(attempt: 2))
|
|
@comment3 = @submission.submission_comments.create!(valid_attributes.merge(attempt: 2))
|
|
@comment4 = @submission.submission_comments.create!(valid_attributes.merge(attempt: nil))
|
|
end
|
|
|
|
context "when the submission attempt is nil" do
|
|
before(:once) do
|
|
@submission.update!(attempt: nil)
|
|
end
|
|
|
|
it "raises an error if the submission_comment attempt is greater than 1" do
|
|
expect { @submission.submission_comments.create!(valid_attributes.merge(attempt: 2)) }.to raise_error(ActiveRecord::RecordInvalid)
|
|
end
|
|
|
|
it "does not raise an error if the submission_comment attempt is equal to 0" do
|
|
expect { @submission.submission_comments.create!(valid_attributes.merge(attempt: 0)) }.not_to raise_error
|
|
end
|
|
|
|
it "does not raise an error if the submission_comment attempt is equal to 1" do
|
|
expect { @submission.submission_comments.create!(valid_attributes.merge(attempt: 1)) }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
it "can limit comments to the specific attempt" do
|
|
expect(@submission.submission_comments.where(attempt: 1)).to eq [@comment1]
|
|
end
|
|
|
|
it "can have multiple comments" do
|
|
expect(@submission.submission_comments.where(attempt: 2).sort).to eq [@comment2, @comment3]
|
|
end
|
|
|
|
it "can limit the comments to attempts that are nil" do
|
|
expect(@submission.submission_comments.where(attempt: nil)).to eq [@comment4]
|
|
end
|
|
|
|
it "cannot be present? if submission#attempt is nil" do
|
|
@submission.update_column(:attempt, nil) # bypass infer_values callback
|
|
@comment1.reload
|
|
@comment1.attempt = 2
|
|
expect(@comment1).not_to be_valid
|
|
end
|
|
|
|
it "cannot be larger then submission#attempt" do
|
|
@comment1.attempt = @submission.attempt + 1
|
|
expect(@comment1).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe "after_save#update_participation" do
|
|
it "doesn't update participation for a manually posted assignment" do
|
|
@assignment.post_policy.update_attribute(:post_manually, true)
|
|
@assignment.hide_submissions(submission_ids: [@submission.id])
|
|
|
|
expect(ContentParticipation).to_not receive(:create_or_update)
|
|
@comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
end
|
|
|
|
it "updates participation for an automatically posted assignment" do
|
|
expect(ContentParticipation).to receive(:participate)
|
|
.with({ content: @submission, user: @student, content_item: "comment", workflow_state: "unread" })
|
|
@comment = @submission.add_comment(author: @teacher, comment: "some comment")
|
|
end
|
|
|
|
it "does not update participation for a draft comment" do
|
|
expect(ContentParticipation).to_not receive(:create_or_update)
|
|
.with({ content: @submission, user: @submission.user, workflow_state: "unread" })
|
|
@comment = @submission.add_comment(author: @teacher, comment: "some comment", draft_comment: true)
|
|
end
|
|
end
|
|
|
|
describe "workflow_state" do
|
|
it "is set to active by default" do
|
|
comment = @submission.add_comment(author: @teacher, comment: ":|")
|
|
expect(comment).to be_active
|
|
end
|
|
end
|
|
|
|
describe "#allows_posting_submission?" do
|
|
it "returns true if the comment is hidden and published" do
|
|
comment = @submission.add_comment(author: @teacher, comment: "hi", hidden: true, draft_comment: false)
|
|
expect(comment).to be_allows_posting_submission
|
|
end
|
|
|
|
it "returns false if the comment is not hidden" do
|
|
comment = @submission.add_comment(author: @teacher, comment: "hi", hidden: false, draft_comment: false)
|
|
expect(comment).not_to be_allows_posting_submission
|
|
end
|
|
|
|
it "returns false if the comment is a draft" do
|
|
comment = @submission.add_comment(author: @teacher, comment: "hi", hidden: true, draft_comment: true)
|
|
expect(comment).not_to be_allows_posting_submission
|
|
end
|
|
end
|
|
|
|
describe "finalizing draft comments" do
|
|
let(:assignment) { @course.assignments.create! }
|
|
let(:student) { @user }
|
|
let(:submission) { assignment.submission_for_student(student) }
|
|
let(:teacher) { @course.enroll_teacher(User.create!, enrollment_state: "active").user }
|
|
let(:admin) { @course.root_account.account_users.create!(user: User.create!).user }
|
|
|
|
context "when the associated submission is not yet posted and the assignment is auto-posted" do
|
|
it "posts the submission when an active instructor finalizes a draft comment" do
|
|
comment = submission.add_comment(comment: "hmmmm", draft_comment: true, author: teacher)
|
|
comment.update!(draft: false)
|
|
expect(submission.reload).to be_posted
|
|
end
|
|
|
|
it "posts the submission when an admin finalizes a draft comment" do
|
|
comment = submission.add_comment(comment: "HMMMM", draft_comment: true, author: admin)
|
|
comment.update!(draft: false)
|
|
expect(submission.reload).to be_posted
|
|
end
|
|
|
|
it "does not post the submission when a non-active instructor finalizes a draft comment" do
|
|
comment = submission.add_comment(comment: "hmmmm", draft_comment: true, author: teacher)
|
|
teacher.enrollments.first.destroy
|
|
comment.update!(draft: false)
|
|
expect(submission.reload).not_to be_posted
|
|
end
|
|
|
|
it "does not post the submission if a student somehow creates and finalizes a draft comment" do
|
|
comment = submission.add_comment(comment: "I am the greatest!", draft_comment: true, author: student)
|
|
comment.update!(draft: false)
|
|
expect(submission.reload).not_to be_posted
|
|
end
|
|
|
|
it "does not post the submission if the finalized comment has no author" do
|
|
comment = submission.add_comment(comment: "who am I?", draft_comment: true, skip_author: true)
|
|
comment.update!(draft: false)
|
|
expect(submission.reload).not_to be_posted
|
|
end
|
|
end
|
|
|
|
it "does not update the submission's posted_at when it is already posted" do
|
|
submission.update!(posted_at: 1.hour.ago(Time.zone.now))
|
|
comment = submission.add_comment(comment: "hmmmm", draft_comment: true, author: teacher)
|
|
|
|
expect do
|
|
comment.update!(draft: false)
|
|
end.not_to change {
|
|
submission.reload.posted_at
|
|
}
|
|
end
|
|
|
|
it "does not post the submission when the assignment is manually-posted" do
|
|
assignment.post_policy.update!(post_manually: true)
|
|
|
|
comment = submission.add_comment(comment: "hmmmm", draft_comment: true, author: teacher)
|
|
comment.update!(draft: false)
|
|
expect(submission.reload).not_to be_posted
|
|
end
|
|
end
|
|
end
|