# frozen_string_literal: true

RSpec.describe "bundle check" do
  it "returns success when the Gemfile is satisfied" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "rails"
    G

    bundle :check
    expect(out).to include("The Gemfile's dependencies are satisfied")
  end

  it "works with the --gemfile flag when not in the directory" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "rails"
    G

    bundle "check --gemfile bundled_app/Gemfile", dir: tmp
    expect(out).to include("The Gemfile's dependencies are satisfied")
  end

  it "creates a Gemfile.lock by default if one does not exist" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "rails"
    G

    FileUtils.rm(bundled_app_lock)

    bundle "check"

    expect(bundled_app_lock).to exist
  end

  it "does not create a Gemfile.lock if --dry-run was passed" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "rails"
    G

    FileUtils.rm(bundled_app_lock)

    bundle "check --dry-run"

    expect(bundled_app_lock).not_to exist
  end

  it "prints an error that shows missing gems" do
    system_gems ["rails-2.3.2"], path: default_bundle_path

    gemfile <<-G
      source "https://gem.repo1"
      gem "rails"
    G

    bundle :check, raise_on_error: false
    expect(err).to include("The following gems are missing")
    expect(err).to include(" * rake (#{rake_version})")
    expect(err).to include(" * actionpack (2.3.2)")
    expect(err).to include(" * activerecord (2.3.2)")
    expect(err).to include(" * actionmailer (2.3.2)")
    expect(err).to include(" * activeresource (2.3.2)")
    expect(err).to include(" * activesupport (2.3.2)")
    expect(err).to include("Install missing gems with `bundle install`")
  end

  it "prints an error that shows missing gems if a Gemfile.lock does not exist and a toplevel dependency is missing" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "rails"
    G

    bundle :check, raise_on_error: false
    expect(exitstatus).to be > 0
    expect(err).to include("The following gems are missing")
    expect(err).to include(" * rails (2.3.2)")
    expect(err).to include(" * rake (#{rake_version})")
    expect(err).to include(" * actionpack (2.3.2)")
    expect(err).to include(" * activerecord (2.3.2)")
    expect(err).to include(" * actionmailer (2.3.2)")
    expect(err).to include(" * activeresource (2.3.2)")
    expect(err).to include(" * activesupport (2.3.2)")
    expect(err).to include("Install missing gems with `bundle install`")
  end

  it "prints a generic error if gem git source is not checked out" do
    build_git "foo", path: lib_path("foo")

    bundle_config "path vendor/bundle"

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

    FileUtils.rm_r bundled_app("vendor/bundle")
    bundle :check, raise_on_error: false
    expect(exitstatus).to eq 1
    expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.")
  end

  it "prints a generic message if you changed your lockfile" do
    build_repo2 do
      build_gem "rails_pinned_to_old_activesupport" do |s|
        s.add_dependency "activesupport", "= 1.2.3"
      end
    end

    install_gemfile <<-G
      source "https://gem.repo2"
      gem 'rails'
    G

    gemfile <<-G
      source "https://gem.repo2"
      gem "rails"
      gem "rails_pinned_to_old_activesupport"
    G

    bundle :check, raise_on_error: false
    expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.")
  end

  it "uses the without setting" do
    bundle_config "without foo"
    install_gemfile <<-G
      source "https://gem.repo1"
      group :foo do
        gem "myrack"
      end
    G

    bundle "check"
    expect(out).to include("The Gemfile's dependencies are satisfied")
  end

  it "ensures that gems are actually installed and not just cached" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack", :group => :foo
    G

    bundle_config "without foo"
    bundle :install

    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    bundle "check", raise_on_error: false
    expect(err).to include("* myrack (1.0.0)")
    expect(exitstatus).to eq(1)
  end

  it "ensures that gems are actually installed and not just cached in applications' cache" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    bundle_config "path vendor/bundle"
    bundle :cache

    uninstall_gem("myrack", env: { "GEM_HOME" => vendored_gems.to_s })

    bundle "check", raise_on_error: false
    expect(err).to include("* myrack (1.0.0)")
    expect(exitstatus).to eq(1)
  end

  it "ignores missing gems restricted to other platforms" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
      platforms :#{not_local_tag} do
        gem "activesupport"
      end
    G

    system_gems "myrack-1.0.0", path: default_bundle_path

    lockfile <<-G
      GEM
        remote: https://gem.repo1/
        specs:
          activesupport (2.3.5)
          myrack (1.0.0)

      PLATFORMS
        #{generic_local_platform}
        #{not_local}

      DEPENDENCIES
        myrack
        activesupport
    G

    bundle :check
    expect(out).to include("The Gemfile's dependencies are satisfied")
  end

  it "works with env conditionals" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
      env :NOT_GOING_TO_BE_SET do
        gem "activesupport"
      end
    G

    system_gems "myrack-1.0.0", path: default_bundle_path

    lockfile <<-G
      GEM
        remote: https://gem.repo1/
        specs:
          activesupport (2.3.5)
          myrack (1.0.0)

      PLATFORMS
        #{generic_local_platform}
        #{not_local}

      DEPENDENCIES
        myrack
        activesupport
    G

    bundle :check
    expect(out).to include("The Gemfile's dependencies are satisfied")
  end

  it "outputs an error when the default Gemfile is not found" do
    bundle :check, raise_on_error: false
    expect(exitstatus).to eq(10)
    expect(err).to include("Could not locate Gemfile")
  end

  it "does not output fatal error message" do
    bundle :check, raise_on_error: false
    expect(exitstatus).to eq(10)
    expect(err).not_to include("Unfortunately, a fatal error has occurred. ")
  end

  it "fails when there's no lockfile and frozen is set" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo"
    G

    bundle_config "deployment true"
    bundle "install"
    FileUtils.rm(bundled_app_lock)

    bundle :check, raise_on_error: false
    expect(last_command).to be_failure
    expect(err).to include("Frozen mode is set, but there's no lockfile")
  end

  it "fails when frozen is set and the lockfile is missing a CHECKSUMS entry" do
    system_gems "myrack-1.0.0", path: default_bundle_path

    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    lockfile <<-L
      GEM
        remote: https://gem.repo1/
        specs:
          myrack (1.0.0)

      PLATFORMS
        #{lockfile_platforms}

      DEPENDENCIES
        myrack

      CHECKSUMS

      BUNDLED WITH
        #{Bundler::VERSION}
    L

    bundle :check, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
    expect(exitstatus).to eq(16)
    expect(err).to include("Your lockfile is missing a CHECKSUMS entry for \"myrack\", but can't be updated because frozen mode is set")
    expect(out).not_to include("The Gemfile's dependencies are satisfied")
  end

  it "fails when frozen is set and the Gemfile has changed" do
    install_gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
    G

    gemfile <<-G
      source "https://gem.repo1"
      gem "myrack"
      gem "rails"
    G

    bundle :check, env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false
    expect(exitstatus).to eq(16)
    expect(err).to include("frozen mode is set")
    expect(err).to include("* rails")
    expect(out).not_to include("The Gemfile's dependencies are satisfied")
  end

  describe "when locked" do
    before :each do
      system_gems "myrack-1.0.0"
      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", "1.0"
      G
    end

    it "returns success when the Gemfile is satisfied" do
      bundle :install
      bundle :check
      expect(out).to include("The Gemfile's dependencies are satisfied")
    end

    it "shows what is missing with the current Gemfile if it is not satisfied" do
      FileUtils.rm_r default_bundle_path
      default_system_gems
      bundle :check, raise_on_error: false
      expect(err).to match(/The following gems are missing/)
      expect(err).to include("* myrack (1.0")
    end
  end

  describe "when locked with multiple dependents with different requirements" do
    before :each do
      build_repo4 do
        build_gem "depends_on_myrack" do |s|
          s.add_dependency "myrack", ">= 1.0"
        end
        build_gem "also_depends_on_myrack" do |s|
          s.add_dependency "myrack", "~> 1.0"
        end
        build_gem "myrack"
      end

      gemfile <<-G
        source "https://gem.repo4"
        gem "depends_on_myrack"
        gem "also_depends_on_myrack"
      G

      bundle "lock"
    end

    it "shows what is missing with the current Gemfile without duplications" do
      bundle :check, raise_on_error: false
      expect(err).to match(/The following gems are missing/)
      expect(err).to include("* myrack (1.0").once
    end
  end

  describe "when locked under multiple platforms" do
    before :each do
      build_repo4 do
        build_gem "myrack"
      end

      gemfile <<-G
        source "https://gem.repo4"
        gem "myrack"
      G

      lockfile <<-L
        GEM
          remote: https://gem.repo4/
          specs:
            myrack (1.0)

        PLATFORMS
          ruby
          #{local_platform}

        DEPENDENCIES
          myrack

        BUNDLED WITH
          #{Bundler::VERSION}
      L
    end

    it "shows what is missing with the current Gemfile without duplications" do
      bundle :check, raise_on_error: false
      expect(err).to match(/The following gems are missing/)
      expect(err).to include("* myrack (1.0").once
    end
  end

  describe "when using only scoped rubygems sources" do
    before do
      gemfile <<~G
        source "https://gem.repo2"
        source "https://gem.repo1" do
          gem "myrack"
        end
      G
    end

    it "returns success when the Gemfile is satisfied" do
      system_gems "myrack-1.0.0", path: default_bundle_path
      bundle :check, artifice: "compact_index"
      expect(out).to include("The Gemfile's dependencies are satisfied")
    end
  end

  describe "when using only scoped rubygems sources with indirect dependencies" do
    before do
      build_repo4 do
        build_gem "depends_on_myrack" do |s|
          s.add_dependency "myrack"
        end

        build_gem "myrack"
      end

      gemfile <<~G
        source "https://gem.repo1"
        source "https://gem.repo4" do
          gem "depends_on_myrack"
        end
      G
    end

    it "returns success when the Gemfile is satisfied and generates a correct lockfile" do
      system_gems "depends_on_myrack-1.0", "myrack-1.0", gem_repo: gem_repo4, path: default_bundle_path
      bundle :check, artifice: "compact_index"

      checksums = checksums_section_when_enabled do |c|
        c.checksum gem_repo4, "depends_on_myrack", "1.0"
        c.checksum gem_repo4, "myrack", "1.0"
      end

      expect(out).to include("The Gemfile's dependencies are satisfied")
      expect(lockfile).to eq <<~L
        GEM
          remote: https://gem.repo1/
          specs:

        GEM
          remote: https://gem.repo4/
          specs:
            depends_on_myrack (1.0)
              myrack
            myrack (1.0)

        PLATFORMS
          #{lockfile_platforms}

        DEPENDENCIES
          depends_on_myrack!
        #{checksums}
        BUNDLED WITH
          #{Bundler::VERSION}
      L
    end
  end

  context "with gemspec directive and scoped sources" do
    before do
      build_repo4 do
        build_gem "awesome_print"
      end

      build_repo2 do
        build_gem "dex-dispatch-engine"
      end

      build_lib("bundle-check-issue", path: tmp("bundle-check-issue")) do |s|
        s.write "Gemfile", <<-G
          source "https://localgemserver.test"

          gemspec

          source "https://localgemserver.test/extra" do
            gem "dex-dispatch-engine"
          end
        G

        s.add_dependency "awesome_print"
      end

      bundle "install", artifice: "compact_index_extra", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }, dir: tmp("bundle-check-issue")
    end

    it "does not corrupt lockfile when changing version" do
      version_file = tmp("bundle-check-issue/bundle-check-issue.gemspec")
      File.write(version_file, File.read(version_file).gsub(/s\.version = .+/, "s.version = '9999'"))

      bundle "check --verbose", dir: tmp("bundle-check-issue")

      lockfile = File.read(tmp("bundle-check-issue/Gemfile.lock"))

      checksums = checksums_section_when_enabled(lockfile) do |c|
        c.checksum gem_repo4, "awesome_print", "1.0"
        c.no_checksum "bundle-check-issue", "9999"
        c.checksum gem_repo2, "dex-dispatch-engine", "1.0"
      end

      expect(lockfile).to eq <<~L
        PATH
          remote: .
          specs:
            bundle-check-issue (9999)
              awesome_print

        GEM
          remote: https://localgemserver.test/
          specs:
            awesome_print (1.0)

        GEM
          remote: https://localgemserver.test/extra/
          specs:
            dex-dispatch-engine (1.0)

        PLATFORMS
          #{lockfile_platforms}

        DEPENDENCIES
          bundle-check-issue!
          dex-dispatch-engine!
        #{checksums}
        BUNDLED WITH
          #{Bundler::VERSION}
      L
    end
  end

  context "with scoped and unscoped sources" do
    it "does not corrupt lockfile" do
      build_repo2 do
        build_gem "foo"
        build_gem "wadus"
        build_gem("baz") {|s| s.add_dependency "wadus" }
      end

      build_repo4 do
        build_gem "bar"
      end

      bundle_config "path.system true"

      # Add all gems to ensure all gems are installed so that a bundle check
      # would be successful
      install_gemfile(<<-G, artifice: "compact_index_extra")
        source "https://gem.repo2"

        source "https://gem.repo4" do
          gem "bar"
        end

        gem "foo"
        gem "baz"
      G

      original_lockfile = lockfile

      # Remove "baz" gem from the Gemfile, and bundle install again to generate
      # a functional lockfile with no "baz" dependency or "wadus" transitive
      # dependency
      install_gemfile(<<-G, artifice: "compact_index_extra")
        source "https://gem.repo2"

        source "https://gem.repo4" do
          gem "bar"
        end

        gem "foo"
      G

      # Add back "baz" gem back to the Gemfile, but _crucially_ we do not perform a
      # bundle install
      gemfile(gemfile + 'gem "baz"')

      bundle :check, verbose: true

      # Bundle check should succeed and restore the lockfile to its original
      # state
      expect(lockfile).to eq(original_lockfile)
    end
  end

  describe "BUNDLED WITH" do
    def lock_with(bundler_version = nil)
      lock = <<~L
        GEM
          remote: https://gem.repo1/
          specs:
            myrack (1.0.0)

        PLATFORMS
          #{lockfile_platforms}

        DEPENDENCIES
          myrack
      L

      if bundler_version
        lock += "\nBUNDLED WITH\n   #{bundler_version}\n"
      end

      lock
    end

    before do
      bundle_config "path vendor/bundle"

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

    context "is not present" do
      it "does not change the lock" do
        lockfile lock_with(nil)
        bundle :check
        expect(lockfile).to eq lock_with(nil)
      end
    end

    context "is newer" do
      it "does not change the lock and does not warn" do
        lockfile lock_with(Bundler::VERSION.succ)
        bundle :check
        expect(err).to be_empty
        expect(lockfile).to eq lock_with(Bundler::VERSION.succ)
      end
    end

    context "is older" do
      it "does not change the lock" do
        system_gems "bundler-1.18.0"
        lockfile lock_with("1.18.0")
        bundle :check
        expect(lockfile).to eq lock_with("1.18.0")
      end
    end
  end
end
