# frozen_string_literal: true

RSpec.shared_examples 'raw snippet files' do
  let(:snippet_id) { snippet.id }
  let_it_be(:user) { snippet.author }
  let(:file_path)  { '%2Egitattributes' }
  let(:ref)        { 'master' }

  let_it_be(:user_token) do
    if user.admin?
      create(:personal_access_token, :admin_mode, user: user)
    else
      create(:personal_access_token, user: user)
    end
  end

  subject { get api(api_path, personal_access_token: user_token) }

  context 'with an invalid snippet ID' do
    let(:snippet_id) { non_existing_record_id }

    it 'returns 404' do
      subject

      aggregate_failures do
        expect(response).to have_gitlab_http_status(:not_found)
        expect(json_response['message']).to eq('404 Snippet Not Found')
      end
    end
  end

  context 'with valid params' do
    it 'returns the raw file info' do
      expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original

      subject

      aggregate_failures do
        expect(response).to have_gitlab_http_status(:ok)
        expect(response.media_type).to eq 'text/plain'
        expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
        expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
        expect(response.header['Content-Disposition']).to match 'filename=".gitattributes"'
      end
    end
  end

  context 'with unauthorized user' do
    let(:user_token) { create(:personal_access_token) }

    it 'returns 404' do
      subject

      expect(response).to have_gitlab_http_status(:not_found)
      expect(json_response['message']).to eq('404 Snippet Not Found')
    end
  end

  context 'with invalid params' do
    using RSpec::Parameterized::TableSyntax

    where(:file_path, :ref, :status, :key, :message) do
      '%2Egitattributes'      | 'invalid-ref' | :not_found   | 'message' | '404 Reference Not Found'
      '%2Egitattributes'      | nil           | :not_found   | 'error'   | '404 Not Found'
      '%2Egitattributes'      | ''            | :not_found   | 'error'   | '404 Not Found'

      'doesnotexist.rb'       | 'master'      | :not_found   | 'message' | '404 File Not Found'
      '/does/not/exist.rb'    | 'master'      | :not_found   | 'error'   | '404 Not Found'
      '%2Fetc%2Fpasswd'       | 'master'      | :bad_request | 'error'   | 'file_path should be a valid file path'
    end

    with_them do
      it 'returns the proper response code and message' do
        subject

        expect(response).to have_gitlab_http_status(status)
        expect(json_response[key]).to eq(message)
      end
    end
  end
end

RSpec.shared_examples 'snippet file updates' do
  let(:create_action)     { { action: 'create', file_path: 'foo.txt', content: 'bar' } }
  let(:update_action)     { { action: 'update', file_path: 'CHANGELOG', content: 'bar' } }
  let(:move_action)       { { action: 'move',   file_path: '.old-gitattributes', previous_path: '.gitattributes' } }
  let(:delete_action)     { { action: 'delete', file_path: 'CONTRIBUTING.md' } }
  let(:bad_file_path)     { { action: 'create', file_path: '../../etc/passwd', content: 'bar' } }
  let(:bad_previous_path) { { action: 'create', previous_path: '../../etc/passwd', file_path: 'CHANGELOG', content: 'bar' } }
  let(:invalid_move)      { { action: 'move',   file_path: 'missing_previous_path.txt' } }

  context 'with various snippet file changes' do
    using RSpec::Parameterized::TableSyntax

    where(:is_multi_file, :file_name, :content, :files, :status) do
      true  | nil       | nil   | [create_action]                | :success
      true  | nil       | nil   | [update_action]                | :success
      true  | nil       | nil   | [move_action]                  | :success
      true  | nil       | nil   | [delete_action]                | :success
      true  | nil       | nil   | [create_action, update_action] | :success
      true  | 'foo.txt' | 'bar' | [create_action]                | :bad_request
      true  | 'foo.txt' | 'bar' | nil                            | :bad_request
      true  | nil       | nil   | nil                            | :bad_request
      true  | 'foo.txt' | nil   | [create_action]                | :bad_request
      true  | nil       | 'bar' | [create_action]                | :bad_request
      true  | ''        | nil   | [create_action]                | :bad_request
      true  | nil       | ''    | [create_action]                | :bad_request
      true  | nil       | nil   | [bad_file_path]                | :bad_request
      true  | nil       | nil   | [bad_previous_path]            | :bad_request
      true  | nil       | nil   | [invalid_move]                 | :unprocessable_entity

      false | 'foo.txt' | 'bar' | nil                            | :success
      false | 'foo.txt' | nil   | nil                            | :success
      false | nil       | 'bar' | nil                            | :success
      false | 'foo.txt' | 'bar' | [create_action]                | :bad_request
      false | nil       | nil   | nil                            | :bad_request
      false | nil       | ''    | nil                            | :bad_request
      false | nil       | nil   | [bad_file_path]                | :bad_request
      false | nil       | nil   | [bad_previous_path]            | :bad_request
    end

    with_them do
      before do
        allow_any_instance_of(Snippet).to receive(:multiple_files?).and_return(is_multi_file)
      end

      it 'has the correct response' do
        update_params = {}.tap do |params|
          params[:files]     = files     if files
          params[:file_name] = file_name if file_name
          params[:content]   = content   if content
        end

        update_snippet(params: update_params)

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

    context 'when save fails due to a repository commit error' do
      before do
        allow_next_instance_of(Repository) do |instance|
          allow(instance).to receive(:commit_files).and_raise(Gitlab::Git::CommitError)
        end

        update_snippet(params: { files: [create_action] })
      end

      it 'returns a bad request response' do
        expect(response).to have_gitlab_http_status(:bad_request)
      end
    end
  end
end

RSpec.shared_examples 'snippet non-file updates' do
  it 'updates a snippet non-file attributes' do
    new_description = 'New description'
    new_title       = 'New title'
    new_visibility  = 'internal'

    update_snippet(params: { title: new_title, description: new_description, visibility: new_visibility })

    snippet.reload

    aggregate_failures do
      expect(response).to have_gitlab_http_status(:ok)
      expect(snippet.description).to eq(new_description)
      expect(snippet.visibility).to eq(new_visibility)
      expect(snippet.title).to eq(new_title)
    end
  end
end

RSpec.shared_examples 'snippet individual non-file updates' do
  using RSpec::Parameterized::TableSyntax

  where(:attribute, :updated_value) do
    :description | 'new description'
    :title       | 'new title'
    :visibility  | 'private'
  end

  with_them do
    it 'updates the attribute' do
      params = { attribute => updated_value }

      expect { update_snippet(params: params) }
        .to change { snippet.reload.send(attribute) }.to(updated_value)
    end
  end
end

RSpec.shared_examples 'invalid snippet updates' do
  it 'returns 404 for invalid snippet id', :aggregate_failures do
    update_snippet(snippet_id: non_existing_record_id, params: { title: 'foo' })

    expect(response).to have_gitlab_http_status(:not_found)
    expect(json_response['message']).to eq('404 Snippet Not Found')
  end

  it 'returns 400 for missing parameters' do
    update_snippet

    expect(response).to have_gitlab_http_status(:bad_request)
  end

  it 'returns 400 if content is blank' do
    update_snippet(params: { content: '' })

    expect(response).to have_gitlab_http_status(:bad_request)
  end

  it 'returns 400 if title is blank', :aggregate_failures do
    update_snippet(params: { title: '' })

    expect(response).to have_gitlab_http_status(:bad_request)
    expect(json_response['error']).to eq 'title is empty'
  end
end

RSpec.shared_examples 'snippet access with different users' do
  using RSpec::Parameterized::TableSyntax

  where(:requester, :visibility, :status) do
    :admin   | :public   | :ok
    :admin   | :private  | :ok
    :admin   | :internal | :ok
    :author  | :public   | :ok
    :author  | :private  | :ok
    :author  | :internal | :ok
    :other   | :public   | :ok
    :other   | :private  | :not_found
    :other   | :internal | :ok
    nil      | :public   | :ok
    nil      | :private  | :not_found
    nil      | :internal | :not_found
  end

  with_them do
    let(:snippet) { snippet_for(visibility) }

    it 'returns the correct response' do
      request_user = user_for(requester)

      admin_mode = requester == :admin

      get api(path, request_user, admin_mode: admin_mode)

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

  def user_for(user_type)
    case user_type
    when :author
      user
    when :other
      other_user
    when :admin
      admin
    end
  end

  def snippet_for(snippet_type)
    case snippet_type
    when :private
      private_snippet
    when :internal
      internal_snippet
    when :public
      public_snippet
    end
  end
end

RSpec.shared_examples 'expected response status' do
  it 'returns the correct response' do
    get api(path, personal_access_token: user_token)

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

RSpec.shared_examples 'unauthenticated project snippet access' do
  using RSpec::Parameterized::TableSyntax

  let(:user_token) { nil }

  where(:project_visibility, :snippet_visibility, :status) do
    :public   | :public   | :ok
    :public   | :private  | :not_found
    :public   | :internal | :not_found
    :internal | :public   | :not_found
    :private  | :public   | :not_found
  end

  with_them do
    it_behaves_like 'expected response status'
  end
end

RSpec.shared_examples 'non-member project snippet access' do
  using RSpec::Parameterized::TableSyntax

  where(:project_visibility, :snippet_visibility, :status) do
    :public   | :public   | :ok
    :public   | :internal | :ok
    :internal | :public   | :ok
    :public   | :private  | :not_found
    :private  | :public   | :not_found
  end

  with_them do
    it_behaves_like 'expected response status'
  end
end

RSpec.shared_examples 'member project snippet access' do
  using RSpec::Parameterized::TableSyntax

  before do
    project.add_guest(user)
  end

  where(:project_visibility, :snippet_visibility, :status) do
    :public   | :public   | :ok
    :public   | :internal | :ok
    :internal | :public   | :ok
    :public   | :private  | :ok
    :private  | :public   | :ok
  end

  with_them do
    it_behaves_like 'expected response status'
  end
end

RSpec.shared_examples 'project snippet access levels' do
  let_it_be(:user_token) { create(:personal_access_token, user: user) }

  let(:project) { create(:project, project_visibility) }
  let(:snippet) { create(:project_snippet, :repository, snippet_visibility, project: project) }

  it_behaves_like 'unauthenticated project snippet access'

  it_behaves_like 'non-member project snippet access'

  it_behaves_like 'member project snippet access'
end
