# frozen_string_literal: true

RSpec.describe "Bundler.require" do
  before :each do
    build_lib "one", "1.0.0" do |s|
      s.write "lib/baz.rb", "puts 'baz'"
      s.write "lib/qux.rb", "puts 'qux'"
    end

    build_lib "two", "1.0.0" do |s|
      s.write "lib/two.rb", "puts 'two'"
      s.add_dependency "three", "= 1.0.0"
    end

    build_lib "three", "1.0.0" do |s|
      s.write "lib/three.rb", "puts 'three'"
      s.add_dependency "seven", "= 1.0.0"
    end

    build_lib "four", "1.0.0" do |s|
      s.write "lib/four.rb", "puts 'four'"
    end

    build_lib "five", "1.0.0", no_default: true do |s|
      s.write "lib/mofive.rb", "puts 'five'"
    end

    build_lib "six", "1.0.0" do |s|
      s.write "lib/six.rb", "puts 'six'"
    end

    build_lib "seven", "1.0.0" do |s|
      s.write "lib/seven.rb", "puts 'seven'"
    end

    build_lib "eight", "1.0.0" do |s|
      s.write "lib/eight.rb", "puts 'eight'"
    end

    build_lib "nine", "1.0.0" do |s|
      s.write "lib/nine.rb", "puts 'nine'"
    end

    build_lib "ten", "1.0.0" do |s|
      s.write "lib/ten.rb", "puts 'ten'"
    end

    gemfile <<-G
      source "https://gem.repo1"
      path "#{lib_path}" do
        gem "one", :group => :bar, :require => %w[baz qux]
        gem "two"
        gem "three", :group => :not
        gem "four", :require => false
        gem "five"
        gem "six", :group => "string"
        gem "seven", :group => :not
        gem "eight", :require => true, :group => :require_true
        env "BUNDLER_TEST" => "nine" do
          gem "nine", :require => true
        end
        gem "ten", :install_if => lambda { ENV["BUNDLER_TEST"] == "ten" }
      end
    G
  end

  it "requires the gems" do
    # default group
    run "Bundler.require"
    expect(out).to eq("two")

    # specific group
    run "Bundler.require(:bar)"
    expect(out).to eq("baz\nqux")

    # default and specific group
    run "Bundler.require(:default, :bar)"
    expect(out).to eq("baz\nqux\ntwo")

    # specific group given as a string
    run "Bundler.require('bar')"
    expect(out).to eq("baz\nqux")

    # specific group declared as a string
    run "Bundler.require(:string)"
    expect(out).to eq("six")

    # required in resolver order instead of gemfile order
    run("Bundler.require(:not)")
    expect(out.split("\n").sort).to eq(%w[seven three])

    # test require: true
    run "Bundler.require(:require_true)"
    expect(out).to eq("eight")
  end

  it "allows requiring gems with non standard names explicitly" do
    run "Bundler.require ; require 'mofive'"
    expect(out).to eq("two\nfive")
  end

  it "allows requiring gems which are scoped by env" do
    ENV["BUNDLER_TEST"] = "nine"
    run "Bundler.require"
    expect(out).to eq("two\nnine")
  end

  it "allows requiring gems which are scoped by install_if" do
    ENV["BUNDLER_TEST"] = "ten"
    run "Bundler.require"
    expect(out).to eq("two\nten")
  end

  it "raises an exception if a require is specified but the file does not exist" do
    gemfile <<-G
      source "https://gem.repo1"
      path "#{lib_path}" do
        gem "two", :require => 'fail'
      end
    G

    run "Bundler.require", raise_on_error: false

    expect(err_without_deprecations).to include("cannot load such file -- fail")
  end

  it "displays a helpful message if the required gem throws an error" do
    build_lib "faulty", "1.0.0" do |s|
      s.write "lib/faulty.rb", "raise RuntimeError.new(\"Gem Internal Error Message\")"
    end

    gemfile <<-G
      source "https://gem.repo1"
      path "#{lib_path}" do
        gem "faulty"
      end
    G

    run "Bundler.require", raise_on_error: false
    expect(err).to match("error while trying to load the gem 'faulty'")
    expect(err).to match("Gem Internal Error Message")
  end

  it "doesn't swallow the error when the library has an unrelated error" do
    build_lib "loadfuuu", "1.0.0" do |s|
      s.write "lib/loadfuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")"
    end

    gemfile <<-G
      source "https://gem.repo1"
      path "#{lib_path}" do
        gem "loadfuuu"
      end
    G

    run "Bundler.require", raise_on_error: false

    expect(err_without_deprecations).to include("cannot load such file -- load-bar")
  end

  describe "with namespaced gems" do
    before :each do
      build_lib "jquery-rails", "1.0.0" do |s|
        s.write "lib/jquery/rails.rb", "puts 'jquery/rails'"
      end
    end

    it "requires gem names that are namespaced" do
      gemfile <<-G
        source "https://gem.repo1"
        path '#{lib_path}' do
          gem 'jquery-rails'
        end
      G

      run "Bundler.require"
      expect(out).to eq("jquery/rails")
    end

    it "silently passes if the require fails" do
      build_lib "bcrypt-ruby", "1.0.0", no_default: true do |s|
        s.write "lib/brcrypt.rb", "BCrypt = '1.0.0'"
      end
      gemfile <<-G
        source "https://gem.repo1"

        path "#{lib_path}" do
          gem "bcrypt-ruby"
        end
      G

      cmd = <<-RUBY
        require 'bundler'
        Bundler.require
      RUBY
      ruby(cmd)

      expect(err).to be_empty
    end

    it "does not mangle explicitly given requires" do
      gemfile <<-G
        source "https://gem.repo1"
        path "#{lib_path}" do
          gem 'jquery-rails', :require => 'jquery-rails'
        end
      G

      run "Bundler.require", raise_on_error: false

      expect(err_without_deprecations).to include("cannot load such file -- jquery-rails")
    end

    it "handles the case where regex fails" do
      build_lib "load-fuuu", "1.0.0" do |s|
        s.write "lib/load-fuuu.rb", "raise LoadError.new(\"Could not open library 'libfuuu-1.0': libfuuu-1.0: cannot open shared object file: No such file or directory.\")"
      end

      gemfile <<-G
        source "https://gem.repo1"
        path "#{lib_path}" do
          gem "load-fuuu"
        end
      G

      run "Bundler.require", raise_on_error: false

      expect(err_without_deprecations).to include("libfuuu-1.0").and include("cannot open shared object file")
    end

    it "doesn't swallow the error when the library has an unrelated error" do
      build_lib "load-fuuu", "1.0.0" do |s|
        s.write "lib/load/fuuu.rb", "raise LoadError.new(\"cannot load such file -- load-bar\")"
      end

      gemfile <<-G
        source "https://gem.repo1"
        path "#{lib_path}" do
          gem "load-fuuu"
        end
      G

      run "Bundler.require", raise_on_error: false

      expect(err_without_deprecations).to include("cannot load such file -- load-bar")
    end
  end

  describe "using bundle exec" do
    it "requires the locked gems" do
      bundle "exec ruby -e 'Bundler.require'"
      expect(out).to eq("two")

      bundle "exec ruby -e 'Bundler.require(:bar)'"
      expect(out).to eq("baz\nqux")

      bundle "exec ruby -e 'Bundler.require(:default, :bar)'"
      expect(out).to eq("baz\nqux\ntwo")
    end
  end

  describe "order" do
    before(:each) do
      build_lib "one", "1.0.0" do |s|
        s.write "lib/one.rb", <<-ONE
          if defined?(Two)
            Two.two
          else
            puts "two_not_loaded"
          end
          puts 'one'
        ONE
      end

      build_lib "two", "1.0.0" do |s|
        s.write "lib/two.rb", <<-TWO
          module Two
            def self.two
              puts 'module_two'
            end
          end
          puts 'two'
        TWO
      end
    end

    it "works when the gems are in the Gemfile in the correct order" do
      gemfile <<-G
        source "https://gem.repo1"
        path "#{lib_path}" do
          gem "two"
          gem "one"
        end
      G

      run "Bundler.require"
      expect(out).to eq("two\nmodule_two\none")
    end

    describe "a gem with different requires for different envs" do
      before(:each) do
        build_gem "multi_gem", to_bundle: true do |s|
          s.write "lib/one.rb", "puts 'ONE'"
          s.write "lib/two.rb", "puts 'TWO'"
        end

        install_gemfile <<-G
          source "https://gem.repo1"
          gem "multi_gem", :require => "one", :group => :one
          gem "multi_gem", :require => "two", :group => :two
        G
      end

      it "requires both with Bundler.require(both)" do
        run "Bundler.require(:one, :two)"
        expect(out).to eq("ONE\nTWO")
      end

      it "requires one with Bundler.require(:one)" do
        run "Bundler.require(:one)"
        expect(out).to eq("ONE")
      end

      it "requires :two with Bundler.require(:two)" do
        run "Bundler.require(:two)"
        expect(out).to eq("TWO")
      end
    end

    it "fails when the gems are in the Gemfile in the wrong order" do
      gemfile <<-G
        source "https://gem.repo1"
        path "#{lib_path}" do
          gem "one"
          gem "two"
        end
      G

      run "Bundler.require"
      expect(out).to eq("two_not_loaded\none\ntwo")
    end

    describe "with busted gems" do
      it "should be busted" do
        build_gem "busted_require", to_bundle: true do |s|
          s.write "lib/busted_require.rb", "require 'no_such_file_omg'"
        end

        install_gemfile <<-G
          source "https://gem.repo1"
          gem "busted_require"
        G

        run "Bundler.require", raise_on_error: false

        expect(err_without_deprecations).to include("cannot load such file -- no_such_file_omg")
      end
    end
  end

  it "does not load rubygems gemspecs that are used" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    run <<-R
      path = File.join(Gem.dir, "specifications", "myrack-1.0.0.gemspec")
      contents = File.read(path)
      contents = contents.lines.to_a.insert(-2, "\n  raise 'broken gemspec'\n").join
      File.open(path, "w") do |f|
        f.write contents
      end
    R

    run <<-R
      Bundler.require
      puts "WIN"
    R

    expect(out).to eq("WIN")
  end

  it "does not load git gemspecs that are used" do
    build_git "foo"

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo-1.0")}"
    G

    run <<-R
      path = Gem.loaded_specs["foo"].loaded_from
      contents = File.read(path)
      contents = contents.lines.to_a.insert(-2, "\n  raise 'broken gemspec'\n").join
      File.open(path, "w") do |f|
        f.write contents
      end
    R

    run <<-R
      Bundler.require
      puts "WIN"
    R

    expect(out).to eq("WIN")
  end

  it "does not load plugins" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    create_file "plugins/rubygems_plugin.rb", "puts 'FAIL'"

    run <<~R, env: { "RUBYLIB" => rubylib.unshift(bundled_app("plugins").to_s).join(File::PATH_SEPARATOR) }
      Bundler.require
      puts "WIN"
    R

    expect(out).to eq("WIN")
  end

  it "does not extract gemspecs from application cache packages" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    bundle :cache

    path = cached_gem("myrack-1.0.0")

    run <<-R
      File.open("#{path}", "w") do |f|
        f.write "broken package"
      end
    R

    run <<-R
      Bundler.require
      puts "WIN"
    R

    expect(out).to eq("WIN")
  end
end

RSpec.describe "Bundler.require with platform specific dependencies" do
  it "does not require the gems that are pinned to other platforms" do
    install_gemfile <<-G
      source "https://gem.repo1"

      platforms :#{not_local_tag} do
        gem "platform_specific", :require => "omgomg"
      end

      gem "myrack", "1.0.0"
    G

    run "Bundler.require"
    expect(err).to be_empty
  end

  it "requires gems pinned to multiple platforms, including the current one" do
    install_gemfile <<-G
      source "https://gem.repo1"

      platforms :#{not_local_tag}, :#{local_tag} do
        gem "myrack", :require => "myrack"
      end
    G

    run "Bundler.require; puts MYRACK"

    expect(out).to eq("1.0.0")
    expect(err).to be_empty
  end
end
