# frozen_string_literal: true

#
# Copyright (C) 2013 - 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 'spec_helper'

describe Quizzes::QuizSerializer do

  def quiz_serializer(options={})
    options.reverse_merge!({
      controller: controller,
      scope: @user,
      session: @session
    })
    Quizzes::QuizSerializer.new(@quiz, options)
  end
  let(:quiz) { @quiz }
  let(:context ) { @context }
  let(:serializer) { @serializer }
  let(:host_name) { 'example.com' }
  let(:json) { @json }
  let(:session) { @session }
  let(:controller) do
    ActiveModel::FakeController.new(accepts_jsonapi: true, stringify_json_ids: false)
  end

  before do
    @context = Account.default.courses.new
    @context.id = 1
    @quiz = Quizzes::Quiz.new title: 'test quiz', description: 'default quiz description'
    @quiz.id = 1
    @quiz.context = @context
    @user = User.new
    allow(@quiz).to receive(:locked_for?).and_return false
    allow(@quiz).to receive(:check_right?).and_return true
    @session = double(:[] => nil)
    allow(controller).to receive(:session).and_return session
    allow(controller).to receive(:context).and_return context
    allow(@quiz).to receive(:grants_right?).at_least(:once).and_return true
    allow(@context).to receive(:grants_right?).at_least(:once).and_return true
    @serializer = quiz_serializer
    @json = @serializer.as_json[:quiz]
  end

  [
    :title, :quiz_type, :hide_results, :time_limit,
    :shuffle_answers, :show_correct_answers, :scoring_policy,
    :allowed_attempts, :one_question_at_a_time, :question_count,
    :points_possible, :cant_go_back, :access_code, :ip_filter, :due_at,
    :lock_at, :unlock_at, :published, :show_correct_answers_at,
    :hide_correct_answers_at, :show_correct_answers_last_attempt,
    :has_access_code, :migration_id
  ].each do |attribute|

      it "serializes #{attribute}" do
        expect(json[attribute]).to eq quiz.send(attribute)
      end
    end

  it "serializes mobile_url" do
    expect(json[:mobile_url]).to eq 'http://example.com/courses/1/quizzes/1?force_user=1&persist_headless=1'
  end

  it "serializes html_url" do
    expect(json[:html_url]).to eq 'http://example.com/courses/1/quizzes/1'
  end

  it "serializes timer_autosubmit_disabled" do
    expect(json[:timer_autosubmit_disabled]).to eq quiz.send(:timer_autosubmit_disabled?)
  end

  describe "description" do
    it "serializes description with a formatter if given" do
      @serializer = quiz_serializer(
        serializer_options: {
          description_formatter: -> (_) {return "description from formatter"}
        }
      )
      json = @serializer.as_json[:quiz]

      expect(json[:description]).to eq "description from formatter"
    end

    it "returns desctiption otherwise" do
      expect(json[:description]).to eq quiz.description
    end
  end

  describe "description for locked quiz" do
    it "returns an empty string for students" do
      serializer = quiz_serializer()
      allow(serializer).to receive('quiz_locked_for_user?').and_return true
      allow(serializer).to receive('user_is_student?').and_return true
      json = serializer.as_json[:quiz]
      expect(json[:description]).to eq ''
    end

    it "returns description for non-students" do
      json = serializer.as_json[:quiz]
      allow(serializer).to receive('quiz_locked_for_user?').and_return true
      allow(serializer).to receive('user_is_student?').and_return false
      expect(json[:description]).to eq quiz.description
    end
  end

  it "serializes speed_grader_url" do
    # No assignment, so it should be nil
    expect(json[:speed_grader_url]).to be_nil
    assignment = Assignment.new
    assignment.id = 1
    assignment.context_id = @context.id
    allow(@quiz).to receive(:assignment).and_return assignment

    # nil when quiz is unpublished
    allow(@quiz).to receive(:published?).and_return false
    expect(@serializer.as_json[:quiz][:speed_grader_url]).to be_nil

    # nil when context doesn't allow speedgrader
    allow(@quiz).to receive(:published?).and_return true
    expect(assignment).to receive(:can_view_speed_grader?).with(@user).and_return false
    expect(@serializer.as_json[:quiz][:speed_grader_url]).to be_nil

    expect(assignment).to receive(:can_view_speed_grader?).with(@user).and_return true
    json = @serializer.as_json[:quiz]
    expect(json[:speed_grader_url]).to eq(
      controller.send(:speed_grader_course_gradebook_url, @quiz.context, assignment_id: @quiz.assignment.id)
    )

    # Students shouldn't be able to see speed_grader_url
    allow(@quiz).to receive(:grants_right?).and_return false
    allow(@context).to receive(:grants_right?).and_return false
    expect(@serializer.as_json[:quiz]).not_to have_key :speed_grader_url
  end

  it "doesn't include the section count unless the user can grade" do
    result = true
    allow(quiz.context).to receive(:grants_right?).with(@user, :manage_grades) { result }
    expect(serializer.as_json[:quiz]).to have_key :section_count

    result = false
    expect(serializer.as_json[:quiz]).not_to have_key :section_count
  end

  it "uses available_question_count for question_count" do
    allow(quiz).to receive(:available_question_count).and_return 5
    expect(serializer.as_json[:quiz][:question_count]).to eq 5
  end

  it "sends the message_students_url when user can grade" do
    result = true
    allow(quiz.context).to receive(:grants_right?) { result }
    expect(serializer.as_json[:quiz][:message_students_url]).to eq(
      controller.send(:api_v1_course_quiz_submission_users_message_url, quiz, quiz.context)
    )

    result = false
    expect(serializer.as_json[:quiz]).not_to have_key :message_students_url
  end

  describe "access code" do
    it "is included if the user can grade" do
      expect(quiz.context).to receive(:grants_right?).with(@user, :manage_grades).
        at_least(:once).and_return true
      expect(serializer.as_json[:quiz]).to have_key :access_code
    end

    it "is included if the user can manage" do
      expect(quiz.context).to receive(:grants_right?).with(@user, :manage_assignments).
        at_least(:once).and_return true
      expect(serializer.as_json[:quiz]).to have_key :access_code
    end

    it "is not included if the user can't grade or manage" do
      expect(quiz.context).to receive(:grants_right?).with(@user, :manage_grades).
        at_least(:once).and_return false
      expect(quiz.context).to receive(:grants_right?).with(@user, :manage_assignments).
        at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :access_code
    end
  end

  describe "id" do

    it "stringifys when stringify_json_ids? is true" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(controller).to receive(:stringify_json_ids?).at_least(:once).and_return true
      expect(serializer.as_json[:quiz][:id]).to eq quiz.id.to_s
    end

    it "when stringify_json_ids? is false" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz][:id]).to eq quiz.id
      expect(serializer.as_json[:quiz][:id].is_a?(Integer)).to be_truthy
    end

  end

  describe "lock_info" do
    it "includes lock_info when appropriate" do
      expect(quiz).to receive(:locked_for?).
        with(@user, check_policies: true, context: @context).
        and_return({due_at: true})
      json = quiz_serializer.as_json[:quiz]
      expect(json).to have_key :lock_info
      expect(json).to have_key :lock_explanation
      expect(json[:locked_for_user]).to eq true

      expect(quiz).to receive(:locked_for?).
        with(@user, check_policies: true, context: @context).
        and_return false
      json = quiz_serializer.as_json[:quiz]
      expect(json).not_to have_key :lock_info
      expect(json).not_to have_key :lock_explanation
      expect(json[:locked_for_user]).to eq false
    end

    it "doesn't if skip_lock_tests is on" do
      json = quiz_serializer({
        serializer_options: {
          skip_lock_tests: true
        }
      }).as_json[:quiz]
      expect(json).not_to have_key :lock_info
      expect(json).not_to have_key :lock_explanation
      expect(json).not_to have_key :locked_for_user
    end
  end

  describe "unpublishable" do

    it "is not present unless the user can manage the quiz's assignments" do
      manage_result = true
      allow(context).to receive(:grants_right?).with(@user, :manage_assignments) { manage_result }
      expect(serializer.filter(serializer.class._attributes)).to include :unpublishable

      manage_result = false
      expect(serializer.filter(serializer.class._attributes)).not_to include :unpublishable
    end
  end

  describe "takeable" do
    before { skip }
    before do
      course_with_teacher(active_all: true)
      course_quiz(true)
      quiz_with_graded_submission([], user: @teacher, quiz: @quiz)
      @serializer = quiz_serializer(quiz_submissions: { @quiz.id => @quiz_submission })
    end

    it "is true when there is no quiz submision" do
      Quizzes::QuizSubmission.delete_all
      expect(quiz_serializer.as_json[:quiz][:takeable]).to eq true
    end

    it "is true when quiz_submission is present but not completed" do
      @quiz_submission.workflow_state = "settings_only"
      expect(@serializer.as_json[:quiz][:takeable]).to eq true
    end

    it "is true when the quiz submission is completed but quiz has unlimited attempts" do
      @quiz_submission.workflow_state = "complete"
      @quiz.allowed_attempts = -1
      expect(@serializer.as_json[:quiz][:takeable]).to eq true
    end

    it "is true when quiz submission is completed, !quiz.unlimited_attempts" do
      @quiz_submission.workflow_state = "complete"
      @quiz.allowed_attempts = 2
      # false when attempts left attempts is 0
      expect(@quiz_submission).to receive(:attempts_left).at_least(:once).and_return 0
      expect(@serializer.as_json[:quiz][:takeable]).to eq false
      # true when than attempts left greater than 0
      expect(@quiz_submission).to receive(:attempts_left).at_least(:once).and_return 1
      expect(@serializer.as_json[:quiz][:takeable]).to eq true
    end
  end

  describe "preview_url" do

    it "is only present when the user can grade the quiz" do
      course_with_teacher(active_all: true)
      course_quiz(true)
      expect(quiz_serializer(scope: @teacher).as_json[:quiz][:preview_url]).
        to eq controller.send(:course_quiz_take_url, @quiz.context, @quiz, preview: '1')
      course_with_student(active_all: true, course: @course)
      expect(quiz_serializer(scope: @student).as_json[:quiz]).not_to have_key :preview_url
    end
  end

  describe "links" do

    describe "assignment_group" do

      context "controller accepts_jsonapi?" do
        before { skip }

        it "serialize the assignment group's url when present" do
          allow(@quiz).to receive(:context).and_return course = Account.default.courses.new
          course.id = 1
          @quiz.assignment_group = assignment_group = AssignmentGroup.new
          assignment_group.id = 1
          expect(serializer.as_json[:quiz]['links']['assignment_group']).to eq(
            controller.send(:api_v1_course_assignment_group_url, course.id,
                            assignment_group.id)
          )
        end

        it "doesn't serialize the assignment group's url if not present" do
          expect(serializer.as_json[:quiz]).not_to have_key(:links)
        end
      end

      context "controller doesn't accept jsonapi" do

        it "serialized the assignment_group as assignment_group_id" do
          expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
          expect(serializer.as_json[:quiz]['assignment_group_id']).to be_nil

          group = quiz.assignment_group = AssignmentGroup.new
          group.id = quiz.assignment_group_id = 1
          expect(serializer.as_json[:quiz][:assignment_group_id]).to eq 1
        end
      end
    end

    describe "student_quiz_submissions" do
      before { skip }
      context "when user may grade" do

        it "sends the url for all submissions" do
          course_with_teacher(active_all: true)
          quiz_with_graded_submission([], course: @course)
          serializer = quiz_serializer(scope: @teacher)
          expect(serializer.as_json[:quiz]['links']['student_quiz_submissions']).to eq(
            controller.send(:api_v1_course_quiz_submissions_url, @quiz.context.id, @quiz.id)
          )
        end

        it "sends the url when no student_quiz_submissions are present" do
          course_with_teacher(active_all: true)
          serializer = quiz_serializer(scope: @teacher)
          expect(serializer.as_json[:quiz]['links']['student_quiz_submissions']).to eq(
            controller.send(:api_v1_course_quiz_submissions_url, @quiz.context.id, @quiz.id)
          )
        end

      end

      context "when user may not grade" do

        it "sends nil" do
          course_with_student(active_all: true)
          quiz_with_graded_submission([], user: @student, course: @course)
          serializer = quiz_serializer(scope: @student)
          expect(serializer.as_json[:quiz]['links']['student_quiz_submissions']).to be_nil
        end

      end

    end

    describe "quiz_submission" do
      before { skip }
      it "includes the quiz_submission in the response if it is present" do
        course_with_student(active_all: true)
        quiz_with_graded_submission([], user: @student, course: @course)
        serializer = quiz_serializer(scope: @student)
        json = serializer.as_json
        expect(json['quiz_submissions'].length).to eq 1
        expect(json[:quiz]['links']['quiz_submission']).to eq @quiz_submission.id.to_s
      end
    end

    describe 'quiz_reports' do
      it 'sends the url' do
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz]['links']['quiz_reports']).to eq(
          controller.send(:api_v1_course_quiz_reports_url, quiz.context.id, quiz.id)
        )
      end

      it 'sends the url as quiz_reports_url' do
        expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz][:quiz_reports_url]).to eq(
          controller.send(:api_v1_course_quiz_reports_url, quiz.context.id, quiz.id)
        )
      end
    end

    describe "quiz_statistics" do
      it "sends the url" do
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz]['links']['quiz_statistics']).to eq(
          controller.send(:api_v1_course_quiz_statistics_url, quiz.context.id, quiz.id)
        )
      end

      it "sends the url in non-JSONAPI too" do
        expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
        allow(quiz).to receive_messages(context: Account.default.courses.create!)
        expect(serializer.as_json[:quiz][:quiz_statistics_url]).to eq(
          controller.send(:api_v1_course_quiz_statistics_url, quiz.context.id, quiz.id)
        )
      end
    end

    describe "submitted_students" do
      before { skip }
      it "sends nil if user can't grade" do
        course_with_student(active_all: true)
        @quiz.unstub(:check_right?)
        @quiz.unstub(:grants_right?)
        @quiz.context.unstub(:grants_right?)
        serializer = quiz_serializer(scope: @student)
        expect(serializer.as_json[:quiz]['links']).not_to have_key 'unsubmitted_students'
      end

      it "sends a url if there are submissions and user can grade" do
        course_with_teacher(active_all: true)
        course_with_student(active_all: true, course: @course)
        quiz_with_graded_submission([], user: @student, course: @course)
        serializer = quiz_serializer(scope: @teacher)
        expect(serializer.as_json[:quiz]['links']['submitted_students']).
          to eq controller.send(:api_v1_course_quiz_submission_users_url,
                                    @quiz.context,
                                    @quiz,
                                    submitted: true)
      end
    end

    describe "unsubmitted_students" do
      before { skip }
      it "sends nil if user can't grade" do
        @quiz.unstub(:check_right?)
        @quiz.unstub(:grants_right?)
        @quiz.context.unstub(:grants_right?)
        course_with_student(active_all: true)
        serializer = quiz_serializer(scope: @student)
        expect(serializer.as_json[:quiz]['links']).not_to have_key 'unsubmitted_students'
      end

      it "sends a url if there are submissions and user can grade" do
        course_with_teacher(active_all: true)
        course_with_student(active_all: true, course: @course)
        course_with_student(active_all: true, course: @course)
        quiz_with_graded_submission([], user: @student, course: @course)
        serializer = quiz_serializer(scope: @teacher)
        expect(serializer.as_json[:quiz]['links']['unsubmitted_students']).
          to eq controller.send(:api_v1_course_quiz_submission_users_url,
                                    @quiz.context,
                                    @quiz,
                                    submitted: 'false')
      end
    end
  end

  describe "quiz_submission_html_url" do
    it "includes a url to the quiz_submission html only if JSONAPI request" do
      expect(serializer.as_json[:quiz][:quiz_submission_html_url]).to eq(
        controller.send(:course_quiz_submission_html_url, context.id, quiz.id)
      )
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submission_html_url
    end
  end

  describe "quiz_submissions_zip_url" do
    it "includes a url to download all files" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return true
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return true
      expect(serializer).to receive(:has_file_uploads?).at_least(:once).and_return true
      expect(serializer.as_json[:quiz][:quiz_submissions_zip_url]).to eq(
        'http://example.com/courses/1/quizzes/1/submissions?zip=1'
      )
    end

    it "doesn't if it's not a JSON-API request" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return false
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return true
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submissions_zip_url
    end

    it "doesn't if the user may not grade" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return true
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submissions_zip_url
    end

    it "doesn't if the quiz has no file upload questions" do
      expect(controller).to receive(:accepts_jsonapi?).at_least(:once).and_return true
      expect(serializer).to receive(:user_may_grade?).at_least(:once).and_return true
      expect(serializer).to receive(:has_file_uploads?).at_least(:once).and_return false
      expect(serializer.as_json[:quiz]).not_to have_key :quiz_submissions_zip_url
    end
  end

  describe "permissions" do
    it "serializes permissions" do
      expect(serializer.as_json[:quiz][:permissions]).to eq({
        read: true,
        submit: true,
        review_grades: true,
        create: true,
        update: true,
        read_statistics: true,
        view_answer_audits: true,
        manage: true,
        delete: true,
        grade: true,
        preview: true
      })
    end
  end

  it 'displays overridden dates for students' do
    course_with_student(active_all: true)
    course_quiz(true)
    serializer = quiz_serializer(scope: @student)
    student_overrides = {
      due_at: 5.minutes.from_now,
      lock_at: nil,
      unlock_at: 3.minutes.from_now
    }

    allow(serializer).to receive(:due_dates).and_return [student_overrides]

    output = serializer.as_json[:quiz]
    expect(output).not_to have_key :all_dates

    [ :due_at, :lock_at, :unlock_at ].each do |key|
      expect(output[key]).to eq student_overrides[key]
    end
  end

  it 'displays quiz dates for students if not overridden' do
    student_overrides = []

    expect(quiz).to receive(:due_at).at_least(:once).and_return 5.minutes.from_now
    expect(quiz).to receive(:lock_at).at_least(:once).and_return nil
    expect(quiz).to receive(:unlock_at).at_least(:once).and_return 3.minutes.from_now
    allow(serializer).to receive(:due_dates).and_return student_overrides

    output = serializer.as_json[:quiz]

    [ :due_at, :lock_at, :unlock_at ].each do |key|
      expect(output[key]).to eq quiz.send(key)
    end
  end

  it 'includes all_dates for teachers and observers' do
    quiz.due_at = 1.hour.from_now

    teacher_overrides = [{
      due_at: quiz.due_at,
      lock_at: nil,
      unlock_at: nil,
      base: true
    }, {
      due_at: 30.minutes.from_now,
      lock_at: 1.hour.from_now,
      unlock_at: 10.minutes.from_now,
      title: 'Some Section'
    }]

    expect(quiz).to receive(:due_at).at_least(:once)
    allow(serializer).to receive(:all_dates).and_return teacher_overrides
    allow(serializer).to receive(:include_all_dates?).and_return true

    output = serializer.as_json[:quiz]
    expect(output).to have_key :all_dates
    expect(output[:all_dates].length).to eq 2
    expect(output[:all_dates].detect { |e| e[:base] }).to eq teacher_overrides[0]
    expect(output[:all_dates].detect { |e| !e[:base] }).to eq teacher_overrides[1]
    expect(output[:due_at]).to eq quiz.due_at
  end

  describe "only_visible_to_overrides" do
    context "as a teacher" do
      before :once do
        course_with_teacher(active_all: true)
        course_quiz(true)
      end

      it "returns the value for DA" do
        @quiz.only_visible_to_overrides = true
        json = quiz_serializer(scope: @teacher).as_json
        expect(json[:quiz][:only_visible_to_overrides]).to be_truthy

        @quiz.only_visible_to_overrides = false
        json = quiz_serializer(scope: @teacher).as_json
        expect(json[:quiz]).to have_key :only_visible_to_overrides
        expect(json[:quiz][:only_visible_to_overrides]).to be_falsey
      end
    end

    context "as a student" do
      before :once do
        course_with_student(active_all: true)
        course_quiz(true)
      end

      it "is not in the hash" do
        @quiz.only_visible_to_overrides = true
        json = quiz_serializer(scope: @student).as_json
        expect(json[:quiz]).not_to have_key :only_visible_to_overrides
      end
    end

  end

  it "includes anonymous_submisions if quiz is a survey quiz" do
    expect(json.keys).to_not include(:anonymous_submissions)

    quiz.quiz_type = 'survey'
    quiz.anonymous_submissions = true
    new_json = quiz_serializer.as_json[:quiz]
    expect(new_json[:anonymous_submissions]).to eq true
  end

  it "does not include question_types" do
    expect(json.keys).not_to include(:question_types)
  end
end
