# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
# ====================================================================

require "util"

require "svn/ra"

class SvnRaTest < Test::Unit::TestCase
  include SvnTestUtil

  def setup
    setup_basic
  end

  def teardown
    teardown_basic
  end

  def test_version
    assert_equal(Svn::Core.subr_version, Svn::Ra.version)
  end

  def test_uuid
    Svn::Ra::Session.open(@repos_uri) do |session|
      assert_equal(@repos.fs.uuid, session.uuid)
    end
  end

  def test_open_without_callback
    assert_nothing_raised do
      Svn::Ra::Session.open(@repos_uri).close
    end
  end

  def test_session
    log = "sample log"
    log2 = "sample log2"
    file = "sample.txt"
    src = "sample source"
    path = File.join(@wc_path, file)

    session = nil
    rev1 = nil
    path_props = nil

    make_context(log) do |ctx0|
      config = {}
      path_props = {"my-prop" => "value"}
      callbacks = Svn::Ra::Callbacks.new(ctx0.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config,callbacks) do |session|

        assert_equal(youngest_rev, session.latest_revnum)
        assert_equal(@repos_uri, session.repos_root)

        File.open(path, "w") {|f| f.print(src)}
        ctx0.add(path)
        info = ctx0.ci(@wc_path)
        rev1 = info.revision

        assert_equal(info.revision, session.dated_revision(info.date))
        content, props = session.file(file, info.revision)
        assert_equal(src, content)
        assert_equal([
                       Svn::Core::PROP_ENTRY_COMMITTED_DATE,
                       Svn::Core::PROP_ENTRY_UUID,
                       Svn::Core::PROP_ENTRY_LAST_AUTHOR,
                       Svn::Core::PROP_ENTRY_COMMITTED_REV,
                     ].sort,
                     props.keys.sort)
        entries, props = session.dir("", info.revision)
        assert_equal([file], entries.keys)
        assert(entries[file].file?)
        assert_equal([
                       Svn::Core::PROP_ENTRY_COMMITTED_DATE,
                       Svn::Core::PROP_ENTRY_UUID,
                       Svn::Core::PROP_ENTRY_LAST_AUTHOR,
                       Svn::Core::PROP_ENTRY_COMMITTED_REV,
                     ].sort,
                     props.keys.sort)

        entries, props = session.dir("", info.revision, Svn::Core::DIRENT_KIND)
        assert_equal(Svn::Core::NODE_FILE, entries[file].kind)
        entries, props = session.dir("", info.revision, 0)
        assert_equal(Svn::Core::NODE_UNKNOWN, entries[file].kind)

        make_context(log2) do |ctx|
          File.open(path, "w") {|f| f.print(src * 2)}
          path_props.each do |key, value|
            ctx.prop_set(key, value, path)
          end
          info = ctx.ci(@wc_path)
          rev2 = info.revision

          logs = []
          receiver = Proc.new do |changed_paths, revision, author, date, message|
            logs << [revision, message]
          end
          session.log([file], rev1, rev2, rev2 - rev1 + 1, &receiver)
          assert_equal([
                         [rev1, log],
                         [rev2, log2],
                       ].sort_by {|rev, log| rev},
                       logs.sort_by {|rev, log| rev})

          assert_equal(Svn::Core::NODE_FILE, session.check_path(file))
          assert_equal(Svn::Core::NODE_FILE, session.stat(file).kind)

          assert_equal({
                         rev1 => "/#{file}",
                         rev2 => "/#{file}",
                       },
                       session.locations(file, [rev1, rev2]))

          infos = []
          session.file_revs(file, rev1, rev2) do |_path, rev, rev_props, prop_diffs|
            hashed_prop_diffs = {}
            prop_diffs.each do |prop|
              hashed_prop_diffs[prop.name] = prop.value
            end
            infos << [rev, _path, hashed_prop_diffs]
          end
          assert_equal([
                         [rev1, "/#{file}", {}],
                         [rev2, "/#{file}", path_props],
                       ],
                       infos)

          infos = []
          session.file_revs2(file, rev1, rev2) do |_path, rev, rev_props, prop_diffs|
            infos << [rev, _path, prop_diffs]
          end
          assert_equal([
                         [rev1, "/#{file}", {}],
                         [rev2, "/#{file}", path_props],
                       ],
                       infos)

          assert_equal({}, session.get_locks(""))
          locks = []
          session.lock({file => rev2}) do |_path, do_lock, lock, ra_err|
            locks << [_path, do_lock, lock, ra_err]
          end
          assert_equal([file],
                       locks.collect{|_path, *rest| _path}.sort)
          lock = locks.assoc(file)[2]
          assert_equal(["/#{file}"],
                       session.get_locks("").collect{|_path, *rest| _path})
          assert_equal(lock.token, session.get_lock(file).token)
          assert_equal([lock.token],
                       session.get_locks(file).values.collect{|l| l.token})
          session.unlock({file => lock.token})
          assert_equal({}, session.get_locks(file))
        end
      end
    end
  end

  def assert_property_access
    log = "sample log"
    file = "sample.txt"
    path = File.join(@wc_path, file)
    make_context(log) do |ctx|
      config = {}
      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|

        FileUtils.touch(path)
        ctx.add(path)
        info = ctx.commit(@wc_path)

        assert_equal(@author, yield(session, :get, Svn::Core::PROP_REVISION_AUTHOR))
        assert_equal(log, yield(session, :get, Svn::Core::PROP_REVISION_LOG))
        assert_equal([
                       Svn::Core::PROP_REVISION_AUTHOR,
                       Svn::Core::PROP_REVISION_DATE,
                       Svn::Core::PROP_REVISION_LOG,
                     ].sort,
                     yield(session, :list).keys.sort)
        yield(session, :set, Svn::Core::PROP_REVISION_LOG, nil)
        assert_nil(yield(session, :get ,Svn::Core::PROP_REVISION_LOG))
        assert_equal([
                       Svn::Core::PROP_REVISION_AUTHOR,
                       Svn::Core::PROP_REVISION_DATE,
                     ].sort,
                     yield(session, :list).keys.sort)
      end
    end
  end

  def test_prop
    assert_property_access do |session, action, *args|
      case action
      when :get
        key, = args
        session[key]
      when :set
        key, value = args
        session[key] = value
      when :list
        session.properties
      end
    end
  end

  def test_prop_with_no_ruby_way
    assert_property_access do |session, action, *args|
      case action
      when :get
        key, = args
        session.prop(key)
      when :set
        key, value = args
        session.set_prop(key, value)
      when :list
        session.proplist
      end
    end
  end

  def test_callback
    log = "sample log"
    file = "sample.txt"
    src1 = "sample source1"
    src2 = "sample source2"
    path = File.join(@wc_path, file)
    path_in_repos = "/#{file}"
    make_context(log) do |ctx|
      config = {}
      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|

        File.open(path, "w") {|f| f.print(src1)}
        ctx.add(path)
        rev1 = ctx.ci(@wc_path).revision

        File.open(path, "w") {|f| f.print(src2)}
        rev2 = ctx.ci(@wc_path).revision

        ctx.up(@wc_path)

        editor, editor_baton = session.commit_editor(log) {}
        reporter = session.update(rev2, "", editor, editor_baton)
        reporter.abort_report

        editor, editor_baton = session.commit_editor(log) {}
        reporter = session.update2(rev2, "", editor)
        reporter.abort_report
      end
    end
  end

  def test_diff
    log = "sample log"
    file = "sample.txt"
    src1 = "a\nb\nc\nd\ne\n"
    src2 = "a\nb\nC\nd\ne\n"
    path = File.join(@wc_path, file)
    path_in_repos = "/#{file}"
    make_context(log) do |ctx|
      config = {}
      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|

        File.open(path, "w") {|f| f.print(src1)}
        ctx.add(path)
        rev1 = ctx.ci(@wc_path).revision

        File.open(path, "w") {|f| f.print(src2)}
        rev2 = ctx.ci(@wc_path).revision

        ctx.up(@wc_path)

        editor = Svn::Delta::BaseEditor.new # dummy
        reporter = session.diff(rev2, "", @repos_uri, editor)
        reporter.set_path("", rev1, false, nil)
        reporter.finish_report
      end
    end
  end

  def test_commit_editor
    log = "sample log"
    dir_uri = "/test"
    config = {}
    make_context(log) do |ctx|
      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|
        actual_info = nil
        actual_date = nil

        expected_info = [1, @author]
        start_time = Time.now
        gc_disable do
          editor, baton = session.commit_editor(log) do |rev, date, author|
            actual_info = [rev, author]
            actual_date = date
          end
          editor.baton = baton

          root = editor.open_root(-1)
          editor.add_directory(dir_uri, root, nil, -1)
          gc_enable do
            GC.start
            editor.close_edit
          end
          assert_equal(expected_info, actual_info)
          assert_operator(start_time..(Time.now), :include?, actual_date)
        end
      end
    end
  end

  def test_commit_editor2
    log = "sample log"
    dir_uri = "/test"
    config = {}
    make_context(log) do |ctx|
      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|
        actual_info = nil
        actual_date = nil

        expected_info = [1, @author]
        start_time = Time.now
        gc_disable do
          editor = session.commit_editor2(log) do |info|
            actual_info = [info.revision, info.author]
            actual_date = info.date
          end

          root = editor.open_root(-1)
          editor.add_directory(dir_uri, root, nil, -1)
          gc_enable do
            GC.start
            editor.close_edit
          end
          assert_equal(expected_info, actual_info)
          assert_operator(start_time..(Time.now), :include?, actual_date)
        end
      end
    end
  end

  def test_reparent
    log = "sample log"
    dir = "dir"
    deep_dir = "deep"
    dir_path = File.join(@wc_path, dir)
    deep_dir_path = File.join(dir_path, deep_dir)
    config = {}
    make_context(log) do |ctx|

      ctx.mkdir(dir_path)
      ctx.ci(@wc_path)

      ctx.mkdir(deep_dir_path)
      ctx.ci(@wc_path)

      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|

        entries, props = session.dir(dir, nil)
        assert_equal([deep_dir], entries.keys)
        assert_raise(Svn::Error::FS_NOT_FOUND) do
          session.dir(deep_dir)
        end

        session.reparent("#{@repos_uri}/#{dir}")
        assert_raise(Svn::Error::FS_NOT_FOUND) do
          session.dir(dir)
        end
        entries, props = session.dir(deep_dir)
        assert_equal([], entries.keys)

        assert_raise(Svn::Error::RA_ILLEGAL_URL) do
          session.reparent("file:///tmp/xxx")
        end
      end
    end
  end

  def test_mergeinfo
    log = "sample log"
    file = "sample.txt"
    src = "sample\n"
    config = {}
    trunk = File.join(@wc_path, "trunk")
    branch = File.join(@wc_path, "branch")
    trunk_path = File.join(trunk, file)
    branch_path = File.join(branch, file)
    session_relative_trunk_path = "trunk"
    repository_branch_path = "/branch"

    make_context(log) do |ctx|
      ctx.mkdir(trunk, branch)
      File.open(trunk_path, "w") {}
      File.open(branch_path, "w") {}
      ctx.add(trunk_path)
      ctx.add(branch_path)
      rev1 = ctx.commit(@wc_path).revision

      File.open(branch_path, "w") {|f| f.print(src)}
      rev2 = ctx.commit(@wc_path).revision

      callbacks = Svn::Ra::Callbacks.new(ctx.auth_baton)
      Svn::Ra::Session.open(@repos_uri, config, callbacks) do |session|

        assert_nil(session.mergeinfo(session_relative_trunk_path))
        ctx.merge(branch, rev1, branch, rev2, trunk)
        assert_nil(session.mergeinfo(session_relative_trunk_path))

        rev3 = ctx.commit(@wc_path).revision
        mergeinfo = session.mergeinfo(session_relative_trunk_path)
        assert_equal([session_relative_trunk_path], mergeinfo.keys)
        trunk_mergeinfo = mergeinfo[session_relative_trunk_path]
        assert_equal([repository_branch_path], trunk_mergeinfo.keys)
        assert_equal([[1, 2, true]],
                     trunk_mergeinfo[repository_branch_path].collect {|range|
                        range.to_a})

        ctx.rm(branch_path)
        rev4 = ctx.commit(@wc_path).revision

        ctx.merge(branch, rev3, branch, rev4, trunk)
        assert(!File.exist?(trunk_path))
        rev5 = ctx.commit(@wc_path).revision

        mergeinfo = session.mergeinfo(session_relative_trunk_path, rev5)
        assert_equal([session_relative_trunk_path], mergeinfo.keys)
        trunk_mergeinfo = mergeinfo[session_relative_trunk_path]
        assert_equal([repository_branch_path], trunk_mergeinfo.keys)
        assert_equal([[1, 2, true], [3, 4, true]],
                     trunk_mergeinfo[repository_branch_path].collect {|range|
                        range.to_a})
      end
    end
  end
end
