require "pact_broker/api/decorators/webhook_decorator"
require "pact_broker/domain/webhook"

module PactBroker
  module Api
    module Decorators
      describe WebhookDecorator do
        let(:description) { "Trigger build" }
        let(:headers) { { :'content-type' => "application/json" } }
        let(:request) do
          {
            method: "POST",
            url: "http://example.org/hook",
            headers: headers,
            body: { some: "body" }
          }
        end

        let(:webhook_request) do
          Webhooks::WebhookRequestTemplate.new(request)
        end

        let(:consumer) { Domain::WebhookPacticipant.new(name: "Consumer") }
        let(:provider) { Domain::WebhookPacticipant.new(name: "Provider") }
        let(:event)    { Webhooks::WebhookEvent.new(name: "something_happened") }
        let(:created_at) { DateTime.now }
        let(:updated_at) { created_at + 1 }

        let(:webhook) do
          Domain::Webhook.new(
            description: description,
            request: webhook_request,
            uuid: "some-uuid",
            consumer: consumer,
            provider: provider,
            events: [event],
            created_at: created_at,
            updated_at: updated_at,
            enabled: false
          )
        end

        subject { WebhookDecorator.new(webhook) }

        describe "to_json" do
          let(:parsed_json) { JSON.parse(subject.to_json(user_options: { base_url: "http://example.org" }), symbolize_names: true) }

          it "includes the description" do
            expect(parsed_json[:description]).to eq "Trigger build"
          end

          it "includes the request" do
            expect(parsed_json[:request]).to eq request
          end

          it "includes the consumer" do
            expect(parsed_json[:consumer]).to eq name: "Consumer"
          end

          it "includes the provider" do
            expect(parsed_json[:provider]).to eq name: "Provider"
          end

          it "includes a link to the consumer" do
            expect(parsed_json[:_links][:'pb:consumer'][:name]).to eq "Consumer"
            expect(parsed_json[:_links][:'pb:consumer'][:href]).to eq "http://example.org/pacticipants/Consumer"
          end

          it "includes a link to the provider" do
            expect(parsed_json[:_links][:'pb:provider'][:name]).to eq "Provider"
            expect(parsed_json[:_links][:'pb:provider'][:href]).to eq "http://example.org/pacticipants/Provider"
          end

          it "includes a link to itself" do
            expect(parsed_json[:_links][:self][:href]).to eq "http://example.org/webhooks/some-uuid"
            expect(parsed_json[:_links][:self][:title]).to_not be_nil
          end

          it "includes a link to its parent collection" do
            expect(parsed_json[:_links][:'pb:pact-webhooks'][:href]).to_not be_nil
          end

          it "includes a link to the webhooks resource" do
            expect(parsed_json[:_links][:'pb:webhooks'][:href]).to_not be_nil
          end

          it "includes a link to execute the webhook directly" do
            expect(parsed_json[:_links][:'pb:execute'][:href]).to eq "http://example.org/webhooks/some-uuid/execute"
          end

          it "includes the events" do
            expect(parsed_json[:events].first).to eq name: "something_happened"
          end

          it "includes timestamps" do
            expect(parsed_json[:createdAt]).to eq FormatDateTime.call(created_at)
            expect(parsed_json[:updatedAt]).to eq FormatDateTime.call(updated_at)
          end

          it "includes the enabled flag" do
            expect(parsed_json[:enabled]).to eq false
          end

          context "when the headers are empty" do
            let(:headers) { nil }
            it "does not include the headers" do
              expect(parsed_json[:request]).to_not have_key :headers
            end
          end

          context "when there is no consumer" do
            before do
              webhook.consumer = nil
            end

            it "does not include the consumer relation" do
              expect(parsed_json[:_links][:'pb:consumer']).to be nil
            end

            it "does not include the pact webhooks relation" do
              expect(parsed_json[:_links][:'pb:pact-webhooks']).to be nil
            end
          end

          context "when there is no provider" do
            before do
              webhook.provider = nil
            end

            it "does not include the provider relation" do
              expect(parsed_json[:_links][:'pb:provider']).to be nil
            end

            it "does not include the pact webhooks relation" do
              expect(parsed_json[:_links][:'pb:pact-webhooks']).to be nil
            end
          end

          context "when the headers contain sensitve information" do
            let(:headers) { { "Authorization" => "foo" } }
            it "redacts them" do
              expect(parsed_json[:request][:headers][:'authorization']).to eq "**********"
            end
          end

          context "when the description is empty" do
            let(:description) { " " }

            it "uses the scope description" do
              expect(parsed_json[:description]).to match(/example.org/)
            end
          end
        end

        describe "from_json" do
          let(:hash) { { request: request, events: [event] } }
          let(:event) { {name: "something_happened"} }
          let(:json) { hash.to_json }
          let(:webhook) { Domain::Webhook.new }
          let(:parsed_object) { subject.from_json(json) }

          it "parses the request method" do
            expect(parsed_object.request.method).to eq "POST"
          end

          it "parses the request URL" do
            expect(parsed_object.request.url).to eq "http://example.org/hook"
          end

          it "parses the request headers" do
            expect(parsed_object.request.headers).to eq "content-type" => "application/json"
          end

          it "parses the request body" do
            expect(parsed_object.request.body).to eq "some" => "body"
          end

          it "parses the events" do
            expect(parsed_object.events.size).to eq 1
            expect(parsed_object.events.first.name).to eq "something_happened"
          end

          context "when no events are specified" do
            let(:hash) { { request: request } }
            let(:webhook) { Domain::Webhook.new }

            it "defaults to a single contract_changed event for backwards compatibility" do
              expect(parsed_object.events.size).to eq 1
              expect(parsed_object.events.first.name).to eq PactBroker::Webhooks::WebhookEvent::DEFAULT_EVENT_NAME
            end
          end

          context "when the decorated object has a consumer/provider but the incoming JSON does not" do
            let(:webhook) do
              Domain::Webhook.new(
                consumer: Domain::WebhookPacticipant.new(name: "consumer"),
                provider: Domain::WebhookPacticipant.new(name: "provider")
              )
            end

            it "sets the consumer and provider to nil on the decorated object" do
              expect(parsed_object.consumer).to be nil
              expect(parsed_object.provider).to be nil
            end
          end
        end
      end
    end
  end
end
