# frozen_string_literal: true
require "spec_helper"
require "bundler/mirror"

describe Bundler::Settings::Mirror do
  let(:mirror) { Bundler::Settings::Mirror.new }

  it "returns zero when fallback_timeout is not set" do
    expect(mirror.fallback_timeout).to eq(0)
  end

  it "takes a number as a fallback_timeout" do
    mirror.fallback_timeout = 1
    expect(mirror.fallback_timeout).to eq(1)
  end

  it "takes truthy as a default fallback timeout" do
    mirror.fallback_timeout = true
    expect(mirror.fallback_timeout).to eq(0.1)
  end

  it "takes falsey as a zero fallback timeout" do
    mirror.fallback_timeout = false
    expect(mirror.fallback_timeout).to eq(0)
  end

  it "takes a string with 'true' as a default fallback timeout" do
    mirror.fallback_timeout = "true"
    expect(mirror.fallback_timeout).to eq(0.1)
  end

  it "takes a string with 'false' as a zero fallback timeout" do
    mirror.fallback_timeout = "false"
    expect(mirror.fallback_timeout).to eq(0)
  end

  it "takes a string for the uri but returns an uri object" do
    mirror.uri = "http://localhost:9292"
    expect(mirror.uri).to eq(URI("http://localhost:9292"))
  end

  it "takes an uri object for the uri" do
    mirror.uri = URI("http://localhost:9293")
    expect(mirror.uri).to eq(URI("http://localhost:9293"))
  end

  context "without a uri" do
    it "invalidates the mirror" do
      mirror.validate!
      expect(mirror.valid?).to be_falsey
    end
  end

  context "with an uri" do
    before { mirror.uri = "http://localhost:9292" }

    context "without a fallback timeout" do
      it "is not valid by default" do
        expect(mirror.valid?).to be_falsey
      end

      context "when probed" do
        let(:probe) { double }

        context "with a replying mirror" do
          before do
            allow(probe).to receive(:replies?).and_return(true)
            mirror.validate!(probe)
          end

          it "is valid" do
            expect(mirror.valid?).to be_truthy
          end
        end

        context "with a non replying mirror" do
          before do
            allow(probe).to receive(:replies?).and_return(false)
            mirror.validate!(probe)
          end

          it "is still valid" do
            expect(mirror.valid?).to be_truthy
          end
        end
      end
    end

    context "with a fallback timeout" do
      before { mirror.fallback_timeout = 1 }

      it "is not valid by default" do
        expect(mirror.valid?).to be_falsey
      end

      context "when probed" do
        let(:probe) { double }

        context "with a replying mirror" do
          before do
            allow(probe).to receive(:replies?).and_return(true)
            mirror.validate!(probe)
          end

          it "is valid" do
            expect(mirror.valid?).to be_truthy
          end

          it "is validated only once" do
            allow(probe).to receive(:replies?).and_raise("Only once!")
            mirror.validate!(probe)
            expect(mirror.valid?).to be_truthy
          end
        end

        context "with a non replying mirror" do
          before do
            allow(probe).to receive(:replies?).and_return(false)
            mirror.validate!(probe)
          end

          it "is not valid" do
            expect(mirror.valid?).to be_falsey
          end

          it "is validated only once" do
            allow(probe).to receive(:replies?).and_raise("Only once!")
            mirror.validate!(probe)
            expect(mirror.valid?).to be_falsey
          end
        end
      end
    end
  end
end

describe Bundler::Settings::Mirrors do
  let(:localhost_uri) { URI("http://localhost:9292") }

  context "with a just created mirror" do
    let(:mirrors) do
      probe = double
      allow(probe).to receive(:replies?).and_return(true)
      Bundler::Settings::Mirrors.new(probe)
    end

    it "returns a mirror that contains the source uri for an unknown uri" do
      mirror = mirrors.for("http://rubygems.org/")
      expect(mirror).to eq(Bundler::Settings::Mirror.new("http://rubygems.org/"))
    end

    it "parses a mirror key and returns a mirror for the parsed uri" do
      mirrors.parse("mirror.http://rubygems.org/", localhost_uri)
      expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
    end

    it "parses a relative mirror key and returns a mirror for the parsed http uri" do
      mirrors.parse("mirror.rubygems.org", localhost_uri)
      expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
    end

    it "parses a relative mirror key and returns a mirror for the parsed https uri" do
      mirrors.parse("mirror.rubygems.org", localhost_uri)
      expect(mirrors.for("https://rubygems.org/").uri).to eq(localhost_uri)
    end

    context "with a uri parsed already" do
      before { mirrors.parse("mirror.http://rubygems.org/", localhost_uri) }

      it "takes a mirror fallback_timeout and assigns the timeout" do
        mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "2")
        expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(2)
      end

      it "parses a 'true' fallback timeout and sets the default timeout" do
        mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "true")
        expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(0.1)
      end

      it "parses a 'false' fallback timeout and sets it to zero" do
        mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "false")
        expect(mirrors.for("http://rubygems.org/").fallback_timeout).to eq(0)
      end
    end
  end

  context "with a mirror prober that replies on time" do
    let(:mirrors) do
      probe = double
      allow(probe).to receive(:replies?).and_return(true)
      Bundler::Settings::Mirrors.new(probe)
    end

    context "with a default fallback_timeout for rubygems.org" do
      before do
        mirrors.parse("mirror.http://rubygems.org/", localhost_uri)
        mirrors.parse("mirror.http://rubygems.org.fallback_timeout", "true")
      end

      it "returns localhost" do
        expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
      end
    end

    context "with a mirror for all" do
      before do
        mirrors.parse("mirror.all", localhost_uri)
      end

      context "without a fallback timeout" do
        it "returns localhost uri for rubygems" do
          expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
        end

        it "returns localhost for any other url" do
          expect(mirrors.for("http://whatever.com/").uri).to eq(localhost_uri)
        end
      end
      context "with a fallback timeout" do
        before { mirrors.parse("mirror.all.fallback_timeout", "1") }

        it "returns localhost uri for rubygems" do
          expect(mirrors.for("http://rubygems.org").uri).to eq(localhost_uri)
        end

        it "returns localhost for any other url" do
          expect(mirrors.for("http://whatever.com/").uri).to eq(localhost_uri)
        end
      end
    end
  end

  context "with a mirror prober that does not reply on time" do
    let(:mirrors) do
      probe = double
      allow(probe).to receive(:replies?).and_return(false)
      Bundler::Settings::Mirrors.new(probe)
    end

    context "with a localhost mirror for all" do
      before { mirrors.parse("mirror.all", localhost_uri) }

      context "without a fallback timeout" do
        it "returns localhost" do
          expect(mirrors.for("http://whatever.com").uri).to eq(localhost_uri)
        end
      end

      context "with a fallback timeout" do
        before { mirrors.parse("mirror.all.fallback_timeout", "true") }

        it "returns the source uri, not localhost" do
          expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
        end
      end
    end

    context "with localhost as a mirror for rubygems.org" do
      before { mirrors.parse("mirror.http://rubygems.org/", localhost_uri) }

      context "without a fallback timeout" do
        it "returns the uri that is not mirrored" do
          expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
        end

        it "returns localhost for rubygems.org" do
          expect(mirrors.for("http://rubygems.org/").uri).to eq(localhost_uri)
        end
      end

      context "with a fallback timeout" do
        before { mirrors.parse("mirror.http://rubygems.org/.fallback_timeout", "true") }

        it "returns the uri that is not mirrored" do
          expect(mirrors.for("http://whatever.com").uri).to eq(URI("http://whatever.com/"))
        end

        it "returns rubygems.org for rubygems.org" do
          expect(mirrors.for("http://rubygems.org/").uri).to eq(URI("http://rubygems.org/"))
        end
      end
    end
  end
end

describe Bundler::Settings::TCPSocketProbe do
  let(:probe) { Bundler::Settings::TCPSocketProbe.new }

  context "with a listening TCP Server" do
    def with_server_and_mirror
      server = TCPServer.new("127.0.0.1", 0)
      mirror = Bundler::Settings::Mirror.new("http://localhost:#{server.addr[1]}", 1)
      yield server, mirror
      server.close unless server.closed?
    end

    it "probes the server correctly" do
      with_server_and_mirror do |server, mirror|
        expect(server.closed?).to be_falsey
        expect(probe.replies?(mirror)).to be_truthy
      end
    end

    it "probes falsey when the server is down" do
      with_server_and_mirror do |server, mirror|
        server.close
        expect(probe.replies?(mirror)).to be_falsey
      end
    end
  end

  context "with an invalid mirror" do
    let(:mirror) { Bundler::Settings::Mirror.new("http://127.0.0.127:9292", true) }

    it "fails with a timeout when there is nothing to tcp handshake" do
      expect(probe.replies?(mirror)).to be_falsey
    end
  end
end
