require 'spec_helper'
require 'fakefs/safe'
require 'pact_broker/client/publish_pacts'
require 'json'

module PactBroker
  module Client
    describe PublishPacts do

      # The amount of stubbing that we have to do here indicates this class is doing
      # TOO MUCH and needs to be split up!
      before do
        FakeFS.activate!
        allow(pacts_client).to receive(:publish).and_return(latest_pact_url)
        allow(PactBroker::Client::PactBrokerClient).to receive(:new)
          .with(base_url: pact_broker_base_url, client_options: pact_broker_client_options)
          .and_return(pact_broker_client)
        allow(pact_broker_client).to receive_message_chain(:pacticipants, :versions).and_return(pact_versions_client)
        allow(pact_broker_client).to receive_message_chain(:pacticipants, :versions, :pacts).and_return(pacts_client)
        allow(pacts_client).to receive(:version_published?).and_return(false)
        allow($stdout).to receive(:puts)
        allow(Retry).to receive(:sleep)
        allow(MergePacts).to receive(:call) { | pact_hashes | pact_hashes[0] }
        FileUtils.mkdir_p "spec/pacts"
        File.open("spec/pacts/consumer-provider.json", "w") { |file| file << pact_hash.to_json }
        File.open("spec/pacts/consumer-provider-2.json", "w") { |file| file << pact_hash.to_json }
        File.open("spec/pacts/foo-bar.json", "w") { |file| file << pact_hash_2.to_json }
      end

      after do
        FakeFS.deactivate!
      end

      let(:latest_pact_url) { 'http://example.org/latest/pact' }
      let(:pact_broker_client) { double("PactBroker::Client")}
      let(:pact_file_paths) { ['spec/pacts/consumer-provider.json']}
      let(:consumer_version) { "1.2.3" }
      let(:tags) { nil }
      let(:pact_hash) { {consumer: {name: 'Consumer'}, provider: {name: 'Provider'}, interactions: [] } }
      let(:pact_hash_2) { {consumer: {name: 'Foo'}, provider: {name: 'Bar'}, interactions: [] } }
      let(:pacts_client) { instance_double("PactBroker::ClientSupport::Pacts")}
      let(:pact_versions_client) { instance_double("PactBroker::Client::Versions", tag: false) }
      let(:pact_broker_base_url) { 'http://some-host'}
      let(:pact_broker_client_options) do
        {
          basic_auth: {
            username: 'user',
            password: 'pass'
          }
        }
      end

      subject { PublishPacts.new(pact_broker_base_url, pact_file_paths, consumer_version, tags, pact_broker_client_options) }

      describe "call" do

        it "uses the pact_broker client to publish the given pact" do
          expect(pacts_client).to receive(:publish).with(pact_hash: pact_hash, consumer_version: consumer_version)
          subject.call
        end

        context "when publishing is successful" do

          it "puts the location of the latest pact" do
            allow($stdout).to receive(:puts)
            expect($stdout).to receive(:puts).with(/#{latest_pact_url}/)
            subject.call
          end
          it "returns true" do
            expect(subject.call).to be true
          end
        end

        context "when publishing multiple files with the same consumer/provider" do
          let(:pact_file_paths) { ['spec/pacts/consumer-provider.json','spec/pacts/consumer-provider-2.json']}
          it "merges the files" do
            expect(MergePacts).to receive(:call).with([pact_hash, pact_hash])
            subject.call
          end
        end

        context "when publishing one or more pacts fails" do
          let(:pact_file_paths) { ['spec/pacts/consumer-provider.json','spec/pacts/foo-bar.json']}

          before do
            allow(pacts_client).to receive(:publish).with(
              pact_hash: pact_hash,
              consumer_version: consumer_version
            ).and_raise("an error")

            allow($stderr).to receive(:puts)
          end

          it "logs an message to stderr" do
            expect($stderr).to receive(:puts).with(/Failed to publish Consumer\/Provider pact/)
            subject.call
          end

          it "continues publishing the rest" do
            expect(pacts_client).to receive(:publish).with(
              pact_hash: pact_hash_2, consumer_version: consumer_version)
            subject.call
          end

          it "returns false" do
            expect(subject.call).to be false
          end
        end

        context "when no pact files are specified" do
          let(:pact_file_paths) { [] }
          it "raises a validation error" do
            expect { subject.call }.to raise_error(/No pact files found/)
          end
        end

        context "when consumer_version is blank" do
          let(:consumer_version) { " " }
          it "raises a validation error" do
            expect { subject.call }.to raise_error(/Please specify the consumer_version/)
          end
        end

        context "when pact_broker_base_url is blank" do
          let(:pact_broker_base_url) { " " }
          it "raises a validation errror" do
            expect { subject.call }.to raise_error(/Please specify the pact_broker_base_url/)
          end
        end

        context "when a tag is provided" do
          let(:tags) { ["dev"] }

          it "tags the consumer version" do
            expect(pact_versions_client).to receive(:tag).with({pacticipant: "Consumer",
              version: consumer_version, tag: "dev"})
            subject.call
          end

          it "tags the version before publishing the pact so that there aren't timing issues retrieving pacts by tag" do
            expect(pact_versions_client).to receive(:tag).ordered
            expect(pacts_client).to receive(:publish).ordered
            subject.call
          end

          context "when an error occurs tagging the pact" do

            before do
              allow(pact_versions_client).to receive(:tag).and_raise("an error")
              allow(Retry).to receive(:sleep)
              allow($stderr).to receive(:puts)
            end

            it "retries multiple times" do
              expect(pact_versions_client).to receive(:tag).exactly(3).times
              subject.call
            end

            it "returns false" do
              expect(subject.call).to eq false
            end
          end
        end

        context "when an error occurs every time while publishing a pact" do

          before do
            allow(Retry).to receive(:sleep)
            allow(pacts_client).to receive(:publish).and_raise("an error")
            allow($stderr).to receive(:puts)
          end

          it "retries multiple times" do
            expect(pacts_client).to receive(:publish).exactly(3).times
            subject.call
          end

          it "returns false" do
            expect(subject.call).to eq false
          end
        end

        context "when an error occurs less than the maximum number of retries" do

          before do
            allow(Retry).to receive(:sleep)
            tries = 0
            allow(pacts_client).to receive(:publish) do
              if tries == 0
                tries += 1
                raise "an error"
              else
                latest_pact_url
              end
            end
            allow($stderr).to receive(:puts)
          end

          it "retries multiple times" do
            expect(pacts_client).to receive(:publish).exactly(2).times
            subject.call
          end

          it "returns true" do
            expect(subject.call).to eq true
          end
        end
      end
    end
  end
end