# frozen_string_literal: true

require_relative "helper"
require "rubygems/request_set"
require "rubygems/request_set/lockfile"
require "rubygems/request_set/lockfile/tokenizer"
require "rubygems/request_set/lockfile/parser"

class TestGemRequestSetLockfileParser < Gem::TestCase
  def setup
    super
    @gem_deps_file = "gem.deps.rb"
    @lock_file = File.expand_path "#{@gem_deps_file}.lock"
    @set = Gem::RequestSet.new
  end

  def test_get
    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "\n"
    parser = tokenizer.make_parser nil, nil

    assert_equal :newline, parser.get.first
  end

  def test_get_type_mismatch
    filename = File.expand_path("#{@gem_deps_file}.lock")
    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "foo", filename, 1, 0
    parser = tokenizer.make_parser nil, nil

    e = assert_raise Gem::RequestSet::Lockfile::ParseError do
      parser.get :section
    end

    expected =
      'unexpected token [:text, "foo"], expected :section (at line 1 column 0)'

    assert_equal expected, e.message

    assert_equal 1, e.line
    assert_equal 0, e.column
    assert_equal filename, e.path
  end

  def test_get_type_multiple
    filename = File.expand_path("#{@gem_deps_file}.lock")
    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1
    parser = tokenizer.make_parser nil, nil

    assert parser.get [:text, :section]
  end

  def test_get_type_value_mismatch
    filename = File.expand_path("#{@gem_deps_file}.lock")
    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.new "x", filename, 1
    parser = tokenizer.make_parser nil, nil

    e = assert_raise Gem::RequestSet::Lockfile::ParseError do
      parser.get :text, "y"
    end

    expected =
      'unexpected token [:text, "x"], expected [:text, "y"] (at line 1 column 0)'

    assert_equal expected, e.message

    assert_equal 1, e.line
    assert_equal 0, e.column
    assert_equal File.expand_path("#{@gem_deps_file}.lock"), e.path
  end

  def test_parse
    write_lockfile <<-LOCKFILE.strip
GEM
  remote: #{@gem_repo}
  specs:
    a (2)

PLATFORMS
  #{Gem::Platform::RUBY}

DEPENDENCIES
  a
    LOCKFILE

    platforms = []
    parse_lockfile @set, platforms

    assert_equal [dep("a")], @set.dependencies

    assert_equal [Gem::Platform::RUBY], platforms

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    assert lockfile_set, "could not find a LockSet"

    assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
  end

  def test_parse_dependencies
    write_lockfile <<-LOCKFILE
GEM
  remote: #{@gem_repo}
  specs:
    a (2)

PLATFORMS
  #{Gem::Platform::RUBY}

DEPENDENCIES
  a (>= 1, <= 2)
    LOCKFILE

    platforms = []
    parse_lockfile @set, platforms

    assert_equal [dep("a", ">= 1", "<= 2")], @set.dependencies

    assert_equal [Gem::Platform::RUBY], platforms

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    assert lockfile_set, "could not find a LockSet"

    assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
  end

  def test_parse_DEPENDENCIES_git
    write_lockfile <<-LOCKFILE
GIT
  remote: git://git.example/josevalim/rails-footnotes.git
  revision: 3a6ac1971e91d822f057650cc5916ebfcbd6ee37
  specs:
    rails-footnotes (3.7.9)
      rails (>= 3.0.0)

GIT
  remote: git://git.example/svenfuchs/i18n-active_record.git
  revision: 55507cf59f8f2173d38e07e18df0e90d25b1f0f6
  specs:
    i18n-active_record (0.0.2)
      i18n (>= 0.5.0)

GEM
  remote: http://gems.example/
  specs:
    i18n (0.6.9)
    rails (4.0.0)

PLATFORMS
  ruby

DEPENDENCIES
  i18n-active_record!
  rails-footnotes!
    LOCKFILE

    parse_lockfile @set, []

    expected = [
      dep("i18n-active_record", "= 0.0.2"),
      dep("rails-footnotes",    "= 3.7.9"),
    ]

    assert_equal expected, @set.dependencies
  end

  def test_parse_DEPENDENCIES_git_version
    write_lockfile <<-LOCKFILE
GIT
  remote: git://github.com/progrium/ruby-jwt.git
  revision: 8d74770c6cd92ea234b428b5d0c1f18306a4f41c
  specs:
    jwt (1.1)

GEM
  remote: http://gems.example/
  specs:

PLATFORMS
  ruby

DEPENDENCIES
  jwt (= 1.1)!
    LOCKFILE

    parse_lockfile @set, []

    expected = [
      dep("jwt", "= 1.1"),
    ]

    assert_equal expected, @set.dependencies
  end

  def test_parse_GEM
    write_lockfile <<-LOCKFILE
GEM
  specs:
    a (2)

PLATFORMS
  ruby

DEPENDENCIES
  a
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", ">= 0")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    assert lockfile_set, "found a LockSet"

    assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)
  end

  def test_parse_GEM_remote_multiple
    write_lockfile <<-LOCKFILE
GEM
  remote: https://gems.example/
  remote: https://other.example/
  specs:
    a (2)

PLATFORMS
  ruby

DEPENDENCIES
  a
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", ">= 0")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    assert lockfile_set, "found a LockSet"

    assert_equal %w[a-2], lockfile_set.specs.map(&:full_name)

    assert_equal %w[https://gems.example/ https://other.example/],
                 lockfile_set.specs.flat_map {|s| s.sources.map {|src| src.uri.to_s } }
  end

  def test_parse_GIT
    @set.instance_variable_set :@install_dir, "install_dir"

    write_lockfile <<-LOCKFILE
GIT
  remote: git://example/a.git
  revision: abranch
  specs:
    a (2)
      b (>= 3)
      c

DEPENDENCIES
  a!
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", "= 2")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    refute lockfile_set, "found a LockSet"

    git_set = @set.sets.find do |set|
      Gem::Resolver::GitSet === set
    end

    assert git_set, "could not find a GitSet"

    assert_equal %w[a-2], git_set.specs.values.map(&:full_name)

    assert_equal [dep("b", ">= 3"), dep("c")],
                 git_set.specs.values.first.dependencies

    expected = {
      "a" => %w[git://example/a.git abranch],
    }

    assert_equal expected, git_set.repositories
    assert_equal "install_dir", git_set.root_dir
  end

  def test_parse_GIT_branch
    write_lockfile <<-LOCKFILE
GIT
  remote: git://example/a.git
  revision: 1234abc
  branch: 0-9-12-stable
  specs:
    a (2)
      b (>= 3)

DEPENDENCIES
  a!
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", "= 2")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    refute lockfile_set, "found a LockSet"

    git_set = @set.sets.find do |set|
      Gem::Resolver::GitSet === set
    end

    assert git_set, "could not find a GitSet"

    expected = {
      "a" => %w[git://example/a.git 1234abc],
    }

    assert_equal expected, git_set.repositories
  end

  def test_parse_GIT_ref
    write_lockfile <<-LOCKFILE
GIT
  remote: git://example/a.git
  revision: 1234abc
  ref: 1234abc
  specs:
    a (2)
      b (>= 3)

DEPENDENCIES
  a!
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", "= 2")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    refute lockfile_set, "found a LockSet"

    git_set = @set.sets.find do |set|
      Gem::Resolver::GitSet === set
    end

    assert git_set, "could not find a GitSet"

    expected = {
      "a" => %w[git://example/a.git 1234abc],
    }

    assert_equal expected, git_set.repositories
  end

  def test_parse_GIT_tag
    write_lockfile <<-LOCKFILE
GIT
  remote: git://example/a.git
  revision: 1234abc
  tag: v0.9.12
  specs:
    a (2)
      b (>= 3)

DEPENDENCIES
  a!
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", "= 2")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    refute lockfile_set, "found a LockSet"

    git_set = @set.sets.find do |set|
      Gem::Resolver::GitSet === set
    end

    assert git_set, "could not find a GitSet"

    expected = {
      "a" => %w[git://example/a.git 1234abc],
    }

    assert_equal expected, git_set.repositories
  end

  def test_parse_PATH
    _, _, directory = vendor_gem

    write_lockfile <<-LOCKFILE
PATH
  remote: #{directory}
  specs:
    a (1)
      b (2)

DEPENDENCIES
  a!
    LOCKFILE

    parse_lockfile @set, []

    assert_equal [dep("a", "= 1")], @set.dependencies

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    refute lockfile_set, "found a LockSet"

    vendor_set = @set.sets.find do |set|
      Gem::Resolver::VendorSet === set
    end

    assert vendor_set, "could not find a VendorSet"

    assert_equal %w[a-1], vendor_set.specs.values.map(&:full_name)

    spec = vendor_set.load_spec "a", nil, nil, nil

    assert_equal [dep("b", "= 2")], spec.dependencies
  end

  def test_parse_dependency
    write_lockfile " 1)"

    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
    parser = tokenizer.make_parser nil, nil

    parsed = parser.parse_dependency "a", "="

    assert_equal dep("a", "= 1"), parsed

    write_lockfile ")"

    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
    parser = tokenizer.make_parser nil, nil

    parsed = parser.parse_dependency "a", "2"

    assert_equal dep("a", "= 2"), parsed
  end

  def test_parse_gem_specs_dependency
    write_lockfile <<-LOCKFILE
GEM
  remote: #{@gem_repo}
  specs:
    a (2)
      b (= 3)
      c (~> 4)
      d
      e (~> 5.0, >= 5.0.1)
    b (3-x86_64-linux)

PLATFORMS
  #{Gem::Platform::RUBY}

DEPENDENCIES
  a
    LOCKFILE

    platforms = []
    parse_lockfile @set, platforms

    assert_equal [dep("a")], @set.dependencies

    assert_equal [Gem::Platform::RUBY], platforms

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    assert lockfile_set, "could not find a LockSet"

    assert_equal %w[a-2 b-3], lockfile_set.specs.map(&:full_name)

    expected = [
      Gem::Platform::RUBY,
      Gem::Platform.new("x86_64-linux"),
    ]

    assert_equal expected, lockfile_set.specs.map(&:platform)

    spec = lockfile_set.specs.first

    expected = [
      dep("b", "= 3"),
      dep("c", "~> 4"),
      dep("d"),
      dep("e", "~> 5.0", ">= 5.0.1"),
    ]

    assert_equal expected, spec.dependencies
  end

  def test_parse_missing
    assert_raise(Errno::ENOENT) do
      parse_lockfile @set, []
    end

    lockfile_set = @set.sets.find do |set|
      Gem::Resolver::LockSet === set
    end

    refute lockfile_set
  end

  def write_lockfile(lockfile)
    File.open @lock_file, "w" do |io|
      io.write lockfile
    end
  end

  def parse_lockfile(set, platforms)
    tokenizer = Gem::RequestSet::Lockfile::Tokenizer.from_file @lock_file
    parser = tokenizer.make_parser set, platforms
    parser.parse
  end
end
