require 'spec_helper'

describe 'Git LFS File Locking API' do
  include WorkhorseHelpers

  let(:project) { create(:project) }
  let(:maintainer) { create(:user) }
  let(:developer) { create(:user) }
  let(:guest)     { create(:user) }
  let(:path)      { 'README.md' }
  let(:headers) do
    {
      'Authorization' => authorization
    }.compact
  end

  shared_examples 'unauthorized request' do
    context 'when user is not authorized' do
      let(:authorization) { authorize_user(guest) }

      it 'returns a forbidden 403 response' do
        post_lfs_json url, body, headers

        expect(response).to have_gitlab_http_status(403)
      end
    end
  end

  before do
    allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)

    project.add_developer(maintainer)
    project.add_developer(developer)
    project.add_guest(guest)
  end

  describe 'Create File Lock endpoint' do
    let(:url)           { "#{project.http_url_to_repo}/info/lfs/locks" }
    let(:authorization) { authorize_user(developer) }
    let(:body)          { { path: path } }

    include_examples 'unauthorized request'

    context 'with an existent lock' do
      before do
        lock_file('README.md', developer)
      end

      it 'return an error message' do
        post_lfs_json url, body, headers

        expect(response).to have_gitlab_http_status(409)

        expect(json_response.keys).to match_array(%w(lock message documentation_url))
        expect(json_response['message']).to match(/already locked/)
      end

      it 'returns the existen lock' do
        post_lfs_json url, body, headers

        expect(json_response['lock']['path']).to eq('README.md')
      end
    end

    context 'without an existent lock' do
      it 'creates the lock' do
        post_lfs_json url, body, headers

        expect(response).to have_gitlab_http_status(201)

        expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner))
      end
    end
  end

  describe 'Listing File Locks endpoint' do
    let(:url)           { "#{project.http_url_to_repo}/info/lfs/locks" }
    let(:authorization) { authorize_user(developer) }

    include_examples 'unauthorized request'

    it 'returns the list of locked files' do
      lock_file('README.md', developer)
      lock_file('README', developer)

      do_get url, nil, headers

      expect(response).to have_gitlab_http_status(200)

      expect(json_response['locks'].size).to eq(2)
      expect(json_response['locks'].first.keys).to match_array(%w(id path locked_at owner))
    end
  end

  describe 'List File Locks for verification endpoint' do
    let(:url)           { "#{project.http_url_to_repo}/info/lfs/locks/verify" }
    let(:authorization) { authorize_user(developer) }

    include_examples 'unauthorized request'

    it 'returns the list of locked files grouped by owner' do
      lock_file('README.md', maintainer)
      lock_file('README', developer)

      post_lfs_json url, nil, headers

      expect(response).to have_gitlab_http_status(200)

      expect(json_response['ours'].size).to eq(1)
      expect(json_response['ours'].first['path']).to eq('README')
      expect(json_response['theirs'].size).to eq(1)
      expect(json_response['theirs'].first['path']).to eq('README.md')
    end
  end

  describe 'Delete File Lock endpoint' do
    let!(:lock)         { lock_file('README.md', developer) }
    let(:url)           { "#{project.http_url_to_repo}/info/lfs/locks/#{lock[:id]}/unlock" }
    let(:authorization) { authorize_user(developer) }

    include_examples 'unauthorized request'

    context 'with an existent lock' do
      it 'deletes the lock' do
        post_lfs_json url, nil, headers

        expect(response).to have_gitlab_http_status(200)
      end

      it 'returns the deleted lock' do
        post_lfs_json url, nil, headers

        expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner))
      end

      context 'when a maintainer uses force' do
        let(:authorization) { authorize_user(maintainer) }

        it 'deletes the lock' do
          project.add_maintainer(maintainer)
          post_lfs_json url, { force: true }, headers

          expect(response).to have_gitlab_http_status(200)
        end
      end
    end
  end

  def lock_file(path, author)
    result = Lfs::LockFileService.new(project, author, { path: path }).execute

    result[:lock]
  end

  def authorize_user(user)
    ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
  end

  def post_lfs_json(url, body = nil, headers = nil)
    post(url, params: body.try(:to_json), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
  end

  def do_get(url, params = nil, headers = nil)
    get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
  end

  def json_response
    @json_response ||= JSON.parse(response.body)
  end
end
