require 'spec_helper'
require 'fileutils'

describe Gitlab::Git::Hook do
  before do
    # We need this because in the spec/spec_helper.rb we define it like this:
    # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
    allow_any_instance_of(described_class).to receive(:trigger).and_call_original
  end

  around do |example|
    # TODO move hook tests to gitaly-ruby. Hook will disappear from gitlab-ce
    Gitlab::GitalyClient::StorageSettings.allow_disk_access do
      example.run
    end
  end

  describe "#trigger" do
    set(:project) { create(:project, :repository) }
    let(:repository) { project.repository.raw_repository }
    let(:repo_path) { repository.path }
    let(:hooks_dir) { File.join(repo_path, 'hooks') }
    let(:user) { create(:user) }
    let(:gl_id) { Gitlab::GlId.gl_id(user) }
    let(:gl_username) { user.username }

    def create_hook(name)
      FileUtils.mkdir_p(hooks_dir)
      hook_path = File.join(hooks_dir, name)
      File.open(hook_path, 'w', 0755) do |f|
        f.write(<<~HOOK)
          #!/bin/sh
          exit 0
        HOOK
      end
    end

    def create_failing_hook(name)
      FileUtils.mkdir_p(hooks_dir)
      hook_path = File.join(hooks_dir, name)
      File.open(hook_path, 'w', 0755) do |f|
        f.write(<<~HOOK)
          #!/bin/sh
          echo 'regular message from the hook'
          echo 'error message from the hook' 1>&2
          echo 'error message from the hook line 2' 1>&2
          exit 1
        HOOK
      end
    end

    ['pre-receive', 'post-receive', 'update'].each do |hook_name|
      context "when triggering a #{hook_name} hook" do
        context "when the hook is successful" do
          let(:hook_path) { File.join(hooks_dir, hook_name) }
          let(:gl_repository) { Gitlab::GlRepository.gl_repository(project, false) }
          let(:env) do
            {
              'GL_ID' => gl_id,
              'GL_USERNAME' => gl_username,
              'PWD' => repo_path,
              'GL_PROTOCOL' => 'web',
              'GL_REPOSITORY' => gl_repository
            }
          end

          it "returns success with no errors" do
            create_hook(hook_name)
            hook = described_class.new(hook_name, repository)
            blank = Gitlab::Git::BLANK_SHA
            ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'

            if hook_name != 'update'
              expect(Open3).to receive(:popen3)
                .with(env, hook_path, chdir: repo_path).and_call_original
            end

            status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
            expect(status).to be true
            expect(errors).to be_blank
          end
        end

        context "when the hook is unsuccessful" do
          it "returns failure with errors" do
            create_failing_hook(hook_name)
            hook = described_class.new(hook_name, repository)
            blank = Gitlab::Git::BLANK_SHA
            ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'

            status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
            expect(status).to be false
            expect(errors).to eq("error message from the hook\nerror message from the hook line 2\n")
          end
        end
      end
    end

    context "when the hook doesn't exist" do
      it "returns success with no errors" do
        hook = described_class.new('unknown_hook', repository)
        blank = Gitlab::Git::BLANK_SHA
        ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'

        status, errors = hook.trigger(gl_id, gl_username, blank, blank, ref)
        expect(status).to be true
        expect(errors).to be_nil
      end
    end
  end
end
