# frozen_string_literal: true

RSpec.describe "bundle install from an existing gemspec" do
  before(:each) do
    build_repo2 do
      build_gem "bar"
      build_gem "bar-dev"
    end
  end

  it "should install runtime and development dependencies" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("Gemfile", "source :rubygems\ngemspec")
      s.add_dependency "bar", "=1.0.0"
      s.add_development_dependency "bar-dev", "=1.0.0"
    end
    install_gemfile <<-G
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}'
    G

    expect(the_bundle).to include_gems "bar 1.0.0"
    expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
  end

  it "that is hidden should install runtime and development dependencies" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("Gemfile", "source :rubygems\ngemspec")
      s.add_dependency "bar", "=1.0.0"
      s.add_development_dependency "bar-dev", "=1.0.0"
    end
    FileUtils.mv tmp.join("foo", "foo.gemspec"), tmp.join("foo", ".gemspec")

    install_gemfile <<-G
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}'
    G

    expect(the_bundle).to include_gems "bar 1.0.0"
    expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
  end

  it "should handle a list of requirements" do
    update_repo2 do
      build_gem "baz", "1.0"
      build_gem "baz", "1.1"
    end

    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("Gemfile", "source :rubygems\ngemspec")
      s.add_dependency "baz", ">= 1.0", "< 1.1"
    end
    install_gemfile <<-G
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}'
    G

    expect(the_bundle).to include_gems "baz 1.0"
  end

  it "should raise if there are no gemspecs available" do
    build_lib("foo", :path => tmp.join("foo"), :gemspec => false)

    install_gemfile(<<-G)
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}'
    G
    expect(last_command.bundler_err).to match(/There are no gemspecs at #{tmp.join('foo')}/)
  end

  it "should raise if there are too many gemspecs available" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("foo2.gemspec", build_spec("foo", "4.0").first.to_ruby)
    end

    install_gemfile(<<-G)
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}'
    G
    expect(last_command.bundler_err).to match(/There are multiple gemspecs at #{tmp.join('foo')}/)
  end

  it "should pick a specific gemspec" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("foo2.gemspec", "")
      s.add_dependency "bar", "=1.0.0"
      s.add_development_dependency "bar-dev", "=1.0.0"
    end

    install_gemfile(<<-G)
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
    G

    expect(the_bundle).to include_gems "bar 1.0.0"
    expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :development
  end

  it "should use a specific group for development dependencies" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("foo2.gemspec", "")
      s.add_dependency "bar", "=1.0.0"
      s.add_development_dependency "bar-dev", "=1.0.0"
    end

    install_gemfile(<<-G)
      source "file://#{gem_repo2}"
      gemspec :path => '#{tmp.join("foo")}', :name => 'foo', :development_group => :dev
    G

    expect(the_bundle).to include_gems "bar 1.0.0"
    expect(the_bundle).not_to include_gems "bar-dev 1.0.0", :groups => :development
    expect(the_bundle).to include_gems "bar-dev 1.0.0", :groups => :dev
  end

  it "should match a lockfile even if the gemspec defines development dependencies" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.write("Gemfile", "source 'file://#{gem_repo1}'\ngemspec")
      s.add_dependency "actionpack", "=2.3.2"
      s.add_development_dependency "rake", "=10.0.2"
    end

    Dir.chdir(tmp.join("foo")) do
      bundle "install"
      # This should really be able to rely on $stderr, but, it's not written
      # right, so we can't. In fact, this is a bug negation test, and so it'll
      # ghost pass in future, and will only catch a regression if the message
      # doesn't change. Exit codes should be used correctly (they can be more
      # than just 0 and 1).
      output = bundle("install --deployment")
      expect(output).not_to match(/You have added to the Gemfile/)
      expect(output).not_to match(/You have deleted from the Gemfile/)
      expect(output).not_to match(/install in deployment mode after changing/)
    end
  end

  it "should match a lockfile without needing to re-resolve" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.add_dependency "rack"
    end

    install_gemfile! <<-G
      source "file://#{gem_repo1}"
      gemspec :path => '#{tmp.join("foo")}'
    G

    bundle! "install", :verbose => true

    message = "Found no changes, using resolution from the lockfile"
    expect(out.scan(message).size).to eq(1)
  end

  it "should match a lockfile without needing to re-resolve with development dependencies" do
    simulate_platform java

    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.add_dependency "rack"
      s.add_development_dependency "thin"
    end

    install_gemfile! <<-G
      source "file://#{gem_repo1}"
      gemspec :path => '#{tmp.join("foo")}'
    G

    bundle! "install", :verbose => true

    message = "Found no changes, using resolution from the lockfile"
    expect(out.scan(message).size).to eq(1)
  end

  it "should match a lockfile on non-ruby platforms with a transitive platform dependency" do
    simulate_platform java
    simulate_ruby_engine "jruby"

    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.add_dependency "platform_specific"
    end

    system_gems "platform_specific-1.0-java", :path => :bundle_path, :keep_path => true

    install_gemfile! <<-G
      gemspec :path => '#{tmp.join("foo")}'
    G

    bundle! "update --bundler", :verbose => true
    expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 JAVA"
  end

  it "should evaluate the gemspec in its directory" do
    build_lib("foo", :path => tmp.join("foo"))
    File.open(tmp.join("foo/foo.gemspec"), "w") do |s|
      s.write "raise 'ahh' unless Dir.pwd == '#{tmp.join("foo")}'"
    end

    install_gemfile <<-G
      gemspec :path => '#{tmp.join("foo")}'
    G
    expect(last_command.stdboth).not_to include("ahh")
  end

  it "allows the gemspec to activate other gems" do
    ENV["BUNDLE_PATH__SYSTEM"] = "true"
    # see https://github.com/bundler/bundler/issues/5409
    #
    # issue was caused by rubygems having an unresolved gem during a require,
    # so emulate that
    system_gems %w[rack-1.0.0 rack-0.9.1 rack-obama-1.0]

    build_lib("foo", :path => bundled_app)
    gemspec = bundled_app("foo.gemspec").read
    bundled_app("foo.gemspec").open("w") do |f|
      f.write "#{gemspec.strip}.tap { gem 'rack-obama'; require 'rack-obama' }"
    end

    install_gemfile! <<-G
      gemspec
    G

    expect(the_bundle).to include_gem "foo 1.0"
  end

  it "allows conflicts" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.version = "1.0.0"
      s.add_dependency "bar", "= 1.0.0"
    end
    build_gem "deps", :to_bundle => true do |s|
      s.add_dependency "foo", "= 0.0.1"
    end
    build_gem "foo", "0.0.1", :to_bundle => true

    install_gemfile <<-G
      source "file://#{gem_repo2}"
      gem "deps"
      gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
    G

    expect(the_bundle).to include_gems "foo 1.0.0"
  end

  it "does not break Gem.finish_resolve with conflicts", :rubygems => ">= 2" do
    build_lib("foo", :path => tmp.join("foo")) do |s|
      s.version = "1.0.0"
      s.add_dependency "bar", "= 1.0.0"
    end
    update_repo2 do
      build_gem "deps" do |s|
        s.add_dependency "foo", "= 0.0.1"
      end
      build_gem "foo", "0.0.1"
    end

    install_gemfile! <<-G
      source "file://#{gem_repo2}"
      gem "deps"
      gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
    G

    expect(the_bundle).to include_gems "foo 1.0.0"

    run! "Gem.finish_resolve; puts 'WIN'"
    expect(out).to eq("WIN")
  end

  context "in deployment mode" do
    context "when the lockfile was not updated after a change to the gemspec's dependencies" do
      it "reports that installation failed" do
        build_lib "cocoapods", :path => bundled_app do |s|
          s.add_dependency "activesupport", ">= 1"
        end

        install_gemfile! <<-G
          source "file://#{gem_repo1}"
          gemspec
        G

        expect(the_bundle).to include_gems("cocoapods 1.0", "activesupport 2.3.5")

        build_lib "cocoapods", :path => bundled_app do |s|
          s.add_dependency "activesupport", ">= 1.0.1"
        end

        bundle :install, forgotten_command_line_options(:deployment => true)

        expect(out).to include("changed")
      end
    end
  end

  context "when child gemspecs conflict with a released gemspec" do
    before do
      # build the "parent" gem that depends on another gem in the same repo
      build_lib "source_conflict", :path => bundled_app do |s|
        s.add_dependency "rack_middleware"
      end

      # build the "child" gem that is the same version as a released gem, but
      # has completely different and conflicting dependency requirements
      build_lib "rack_middleware", "1.0", :path => bundled_app("rack_middleware") do |s|
        s.add_dependency "rack", "1.0" # anything other than 0.9.1
      end
    end

    it "should install the child gemspec's deps" do
      install_gemfile <<-G
        source "file://#{gem_repo1}"
        gemspec
      G

      expect(the_bundle).to include_gems "rack 1.0"
    end
  end

  context "with a lockfile and some missing dependencies" do
    let(:source_uri) { "http://localgemserver.test" }

    context "previously bundled for Ruby" do
      let(:platform) { "ruby" }
      let(:explicit_platform) { false }

      before do
        build_lib("foo", :path => tmp.join("foo")) do |s|
          s.add_dependency "rack", "=1.0.0"
        end

        if explicit_platform
          create_file(
            tmp.join("foo", "foo-#{platform}.gemspec"),
            build_spec("foo", "1.0", platform) do
              dep "rack", "=1.0.0"
              @spec.authors = "authors"
              @spec.summary = "summary"
            end.first.to_ruby
          )
        end

        gemfile <<-G
          source "#{source_uri}"
          gemspec :path => "../foo"
        G

        lockfile <<-L
          PATH
            remote: ../foo
            specs:
              foo (1.0)
                rack (= 1.0.0)

          GEM
            remote: #{source_uri}
            specs:
              rack (1.0.0)

          PLATFORMS
            #{generic_local_platform}

          DEPENDENCIES
            foo!

          BUNDLED WITH
             #{Bundler::VERSION}
        L
      end

      context "using JRuby with explicit platform" do
        let(:platform) { "java" }
        let(:explicit_platform) { true }

        it "should install" do
          simulate_ruby_engine "jruby" do
            simulate_platform "java" do
              results = bundle "install", :artifice => "endpoint"
              expect(results).to include("Installing rack 1.0.0")
              expect(the_bundle).to include_gems "rack 1.0.0"
            end
          end
        end
      end

      context "using JRuby" do
        let(:platform) { "java" }

        it "should install" do
          simulate_ruby_engine "jruby" do
            simulate_platform "java" do
              results = bundle "install", :artifice => "endpoint"
              expect(results).to include("Installing rack 1.0.0")
              expect(the_bundle).to include_gems "rack 1.0.0"
            end
          end
        end
      end

      context "using Windows" do
        it "should install" do
          simulate_windows do
            results = bundle "install", :artifice => "endpoint"
            expect(results).to include("Installing rack 1.0.0")
            expect(the_bundle).to include_gems "rack 1.0.0"
          end
        end
      end
    end

    context "bundled for ruby and jruby" do
      let(:platform_specific_type) { :runtime }
      let(:dependency) { "platform_specific" }
      before do
        build_repo2 do
          build_gem "indirect_platform_specific" do |s|
            s.add_runtime_dependency "platform_specific"
          end
        end

        build_lib "foo", :path => "." do |s|
          if platform_specific_type == :runtime
            s.add_runtime_dependency dependency
          elsif platform_specific_type == :development
            s.add_development_dependency dependency
          else
            raise "wrong dependency type #{platform_specific_type}, can only be :development or :runtime"
          end
        end

        %w[ruby jruby].each do |platform|
          simulate_platform(platform) do
            install_gemfile <<-G
              source "file://localhost#{gem_repo2}"
              gemspec
            G
          end
        end
      end

      context "on ruby", :bundler => "< 3" do
        before do
          simulate_platform("ruby")
          bundle :install
        end

        context "as a runtime dependency" do
          it "keeps java dependencies in the lockfile" do
            expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
            expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
              PATH
                remote: .
                specs:
                  foo (1.0)
                    platform_specific

              GEM
                remote: file://localhost#{gem_repo2}/
                specs:
                  platform_specific (1.0)
                  platform_specific (1.0-java)

              PLATFORMS
                java
                ruby

              DEPENDENCIES
                foo!

              BUNDLED WITH
                 #{Bundler::VERSION}
            L
          end
        end

        context "as a development dependency" do
          let(:platform_specific_type) { :development }

          it "keeps java dependencies in the lockfile" do
            expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
            expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
              PATH
                remote: .
                specs:
                  foo (1.0)

              GEM
                remote: file://localhost#{gem_repo2}/
                specs:
                  platform_specific (1.0)
                  platform_specific (1.0-java)

              PLATFORMS
                java
                ruby

              DEPENDENCIES
                foo!
                platform_specific

              BUNDLED WITH
                 #{Bundler::VERSION}
            L
          end
        end

        context "with an indirect platform-specific development dependency" do
          let(:platform_specific_type) { :development }
          let(:dependency) { "indirect_platform_specific" }

          it "keeps java dependencies in the lockfile" do
            expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY"
            expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
              PATH
                remote: .
                specs:
                  foo (1.0)

              GEM
                remote: file://localhost#{gem_repo2}/
                specs:
                  indirect_platform_specific (1.0)
                    platform_specific
                  platform_specific (1.0)
                  platform_specific (1.0-java)

              PLATFORMS
                java
                ruby

              DEPENDENCIES
                foo!
                indirect_platform_specific

              BUNDLED WITH
                 #{Bundler::VERSION}
            L
          end
        end
      end

      context "on ruby", :bundler => "3" do
        before do
          simulate_platform("ruby")
          bundle :install
        end

        context "as a runtime dependency" do
          it "keeps java dependencies in the lockfile" do
            expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
            expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
              GEM
                remote: file://localhost#{gem_repo2}/
                specs:
                  platform_specific (1.0)
                  platform_specific (1.0-java)

              PATH
                remote: .
                specs:
                  foo (1.0)
                    platform_specific

              PLATFORMS
                java
                ruby

              DEPENDENCIES
                foo!

              BUNDLED WITH
                 #{Bundler::VERSION}
            L
          end
        end

        context "as a development dependency" do
          let(:platform_specific_type) { :development }

          it "keeps java dependencies in the lockfile" do
            expect(the_bundle).to include_gems "foo 1.0", "platform_specific 1.0 RUBY"
            expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
              GEM
                remote: file://localhost#{gem_repo2}/
                specs:
                  platform_specific (1.0)
                  platform_specific (1.0-java)

              PATH
                remote: .
                specs:
                  foo (1.0)

              PLATFORMS
                java
                ruby

              DEPENDENCIES
                foo!
                platform_specific

              BUNDLED WITH
                 #{Bundler::VERSION}
            L
          end
        end

        context "with an indirect platform-specific development dependency" do
          let(:platform_specific_type) { :development }
          let(:dependency) { "indirect_platform_specific" }

          it "keeps java dependencies in the lockfile" do
            expect(the_bundle).to include_gems "foo 1.0", "indirect_platform_specific 1.0", "platform_specific 1.0 RUBY"
            expect(lockfile).to eq normalize_uri_file(strip_whitespace(<<-L))
              GEM
                remote: file://localhost#{gem_repo2}/
                specs:
                  indirect_platform_specific (1.0)
                    platform_specific
                  platform_specific (1.0)
                  platform_specific (1.0-java)

              PATH
                remote: .
                specs:
                  foo (1.0)

              PLATFORMS
                java
                ruby

              DEPENDENCIES
                foo!
                indirect_platform_specific

              BUNDLED WITH
                 #{Bundler::VERSION}
            L
          end
        end
      end
    end
  end

  context "with multiple platforms" do
    before do
      build_lib("foo", :path => tmp.join("foo")) do |s|
        s.version = "1.0.0"
        s.add_development_dependency "rack"
        s.write "foo-universal-java.gemspec", build_spec("foo", "1.0.0", "universal-java") {|sj| sj.runtime "rack", "1.0.0" }.first.to_ruby
      end
    end

    it "installs the ruby platform gemspec" do
      simulate_platform "ruby"

      install_gemfile! <<-G
        source "file://#{gem_repo1}"
        gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
      G

      expect(the_bundle).to include_gems "foo 1.0.0", "rack 1.0.0"
    end

    it "installs the ruby platform gemspec and skips dev deps with --without development" do
      simulate_platform "ruby"

      install_gemfile! <<-G, forgotten_command_line_options(:without => "development")
        source "file://#{gem_repo1}"
        gemspec :path => '#{tmp.join("foo")}', :name => 'foo'
      G

      expect(the_bundle).to include_gem "foo 1.0.0"
      expect(the_bundle).not_to include_gem "rack"
    end
  end
end
