# frozen_string_literal: true

#
# Copyright (C) 2016 - 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 "../../../lti_spec_helper"

RSpec.describe Lti::ContentMigrationService::Exporter do
  include WebMock::API
  include LtiSpecHelper

  describe "#export_completed?" do
    let(:course) { course_model }
    let(:tool) do
      course.context_external_tools.create!({
                                              name: "a",
                                              domain: "lti.example.com",
                                              consumer_key: "12345",
                                              shared_secret: "sekret",
                                            })
    end
    let(:migrator) { Lti::ContentMigrationService::Exporter.new(course, tool, {}) }
    let(:status_url) { "https://lti.example.com/export/42/status" }

    before do
      migrator.instance_variable_set(:@status_url, status_url)
    end

    it "must return true when the remote service indicates the export status is completed" do
      stub_request(:get, status_url)
        .to_return(status: 200, body: { status: "completed" }.to_json)
      expect(migrator).to be_export_completed
    end

    it "must raise an exception with the response message when the export has failed" do
      stub_request(:get, status_url)
        .to_return(status: 200, body: { status: "failed", message: "Oh, noes!" }.to_json)
      expect { migrator.export_completed? }
        .to raise_error RuntimeError, "Oh, noes!"
    end

    it "must return false when the remote service indicates the export status neither completed or failed" do
      stub_request(:get, status_url)
        .to_return(status: 200, body: { status: "foobar" }.to_json)
      expect(migrator).to_not be_export_completed
    end
  end

  describe "#retrieve_export" do
    let(:course) { course_model }
    let(:tool) do
      course.context_external_tools.create!({
                                              name: "a",
                                              domain: "lti.example.com",
                                              consumer_key: "12345",
                                              shared_secret: "sekret",
                                            })
    end
    let(:migrator) { Lti::ContentMigrationService::Exporter.new(course, tool, {}) }
    let(:fetch_url) { "https://lti.example.com/export/42" }

    before do
      migrator.instance_variable_set(:@fetch_url, fetch_url)
    end

    it "must return nil when the export status has indicated failure" do
      migrator.instance_variable_set(:@export_status, "failed")
      expect(migrator.retrieve_export).to be_nil
    end

    it "must return the parsed response body when the export status indicated completion" do
      response_body = { foo: "bar", baz: "qux" }.to_json
      stub_request(:get, fetch_url)
        .to_return(status: 200, body: response_body, headers: {})
      migrator.instance_variable_set(:@export_status, "completed")
      expect(migrator.retrieve_export).to eq({ "foo" => "bar", "baz" => "qux" })
    end

    it "must raise an exception when the response status is something other than 200" do
      response_body = { foo: "bar", baz: "qux" }.to_json
      stub_request(:get, fetch_url)
        .to_return(status: 404, body: response_body, headers: {})
      expect { migrator.retrieve_export }
        .to raise_error RuntimeError, /404/
    end
  end

  describe "#start!" do
    let(:export_url) { "https://lti.example.com/begin_export" }

    before do
      # creates @course :-(
      course_model
      @root_account = @course.root_account
      @root_account.ensure_defaults
      @root_account.save!
      @tool = @course.context_external_tools.build({
                                                     name: "a",
                                                     domain: "lti.example.com",
                                                     consumer_key: "12345",
                                                     shared_secret: "secret",
                                                   })
      @tool.settings = Importers::ContextExternalToolImporter.create_tool_settings({
                                                                                     settings: {
                                                                                       "custom_fields" => {
                                                                                         "course_id" => "$Canvas.course.id",
                                                                                       },
                                                                                       "content_migration" => {
                                                                                         "export_start_url" => export_url,
                                                                                         "import_start_url" => "https://lti.example.com/begin_import"
                                                                                       },
                                                                                     }
                                                                                   })
      @tool.save!

      response_body = {
        status_url: "https://lti.example.com/export/42/status",
        fetch_url: "https://lti.example.com/export/42",
      }.to_json
      stub_request(:post, export_url)
        .to_return(status: 200, body: response_body, headers: {})
      @options = {}
      @migrator = Lti::ContentMigrationService::Exporter.new(@course, @tool, @options)
    end

    context "for a full export" do
      before do
        @migrator.start!
      end

      it "must post the context_id to the configured tools" do
        assert_requested(:post, export_url, {
                           body: hash_including(context_id: Lti::V1p1::Asset.opaque_identifier_for(@course))
                         })
      end

      it "must post the tool_consumer_instance_guid to the configured tools" do
        assert_requested(:post, export_url, {
                           body: hash_including(tool_consumer_instance_guid: @root_account.lti_guid)
                         })
      end

      it "must include a JWT as the Authorization header for each request" do
        assert_requested(:post, export_url, {
                           headers: { "Authorization" => /^Bearer [a-zA-Z0-9\-_]{36,}\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]{43}$/ }
                         })
      end

      it "must include any variable expansions requested by the tool" do
        assert_requested(:post, export_url, {
                           body: hash_including("custom_course_id" => @course.id.to_s)
                         })
      end

      it "must not include the exported assets key" do
        assert_not_requested(:post, export_url, {
                               body: hash_including("custom_exported_assets" => anything)
                             })
      end

      it "must mark the migration as successfully started" do
        expect(@migrator).to be_successfully_started
      end

      context "for the default body format" do
        it "must format request body as nested query" do
          assert_requested(:post, export_url, {
                             body: Rack::Utils.build_nested_query({
                                                                    context_id: Lti::V1p1::Asset.opaque_identifier_for(@course),
                                                                    tool_consumer_instance_guid: @root_account.lti_guid,
                                                                    custom_course_id: @course.id.to_s
                                                                  })
                           })
        end

        it "must post with correct content-type" do
          assert_requested(:post, export_url, {
                             headers: { "Content-Type" => "application/x-www-form-urlencoded" }
                           })
        end
      end

      context "for json body format" do
        before do
          @tool.settings = Importers::ContextExternalToolImporter.create_tool_settings({
                                                                                         settings: {
                                                                                           "content_migration" => {
                                                                                             "export_start_url" => export_url,
                                                                                             "import_start_url" => "https://lti.example.com/begin_import",
                                                                                             "export_format" => "json"
                                                                                           },
                                                                                         }
                                                                                       })
          @tool.save!
          @migrator = Lti::ContentMigrationService::Exporter.new(@course, @tool, @options)
          @migrator.start!
        end

        it "must format request body as json" do
          assert_requested(:post, export_url, {
                             body: {
                               context_id: Lti::V1p1::Asset.opaque_identifier_for(@course),
                               tool_consumer_instance_guid: @root_account.lti_guid,
                             }
                           })
        end

        it "must post with correct content-type" do
          assert_requested(:post, export_url, {
                             headers: { "Content-Type" => "application/json" }
                           })
        end
      end
    end

    context "for a partial export" do
      before do
        @options[:selective] = true
        @exports = @options[:exported_assets] = %w[assignment_42 learning_outcome_84 announcement_21]
        @migrator.start!
      end

      it "must include the exported assets in the request to the tool" do
        assert_requested(:post, export_url, {
                           body: hash_including("custom_exported_assets" => @exports)
                         })
      end
    end
  end

  describe "#successfully_started?" do
    let(:migrator) { Lti::ContentMigrationService::Exporter.new("", "", {}) }

    it "must return true when status and fetch urls are both present" do
      migrator.instance_variable_set(:@status_url, "junk")
      migrator.instance_variable_set(:@fetch_url, "junk")
      expect(migrator).to be_successfully_started
    end

    it "must return false when there are not status and fetch urls" do
      expect(migrator).to_not be_successfully_started
    end
  end
end
