# 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 "#request_captions" do
    it "does not call the request_captions method if FF is off" do
      submission_comment = @submission.submission_comments.create!(valid_attributes)
      expect(submission_comment).not_to receive(:request_captions)
      submission_comment.save!
    end

    it "calls the request_captions method if FF is on" do
      account = @submission.context.account
      account.enable_feature!(:submission_comment_media_auto_captioning)
      submission_comment = @submission.submission_comments.create!(valid_attributes)
      expect(submission_comment).to receive(:request_captions)
      submission_comment.save!
    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.reload.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 = 1.minute.ago
    @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(:teacher) { course.enroll_teacher(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
        let(:ta_comment) do
          assignment.update_submission(student, commenter: ta, comment: "ta comment")
          submission.submission_comments.second
        end
        let(:teacher_comment) do
          assignment.update_submission(student, commenter: teacher, comment: "teacher comment")
          submission.submission_comments.third
        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_edit, 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

          it "allows teachers to read the author of their own comments and other graders" do
            expect(comment.grants_right?(teacher, :read_author)).to be false
            expect(ta_comment.grants_right?(teacher, :read_author)).to be true
            expect(teacher_comment.grants_right?(teacher, :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
