# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::API::Admin::Ci::Variables do
  let_it_be(:admin) { create(:admin) }
  let_it_be(:user) { create(:user) }

  describe 'GET /admin/ci/variables' do
    let!(:variable) { create(:ci_instance_variable) }

    it 'returns instance-level variables for admins', :aggregate_failures do
      get api('/admin/ci/variables', admin)

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response).to be_a(Array)
    end

    it 'does not return instance-level variables for regular users' do
      get api('/admin/ci/variables', user)

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

    it 'does not return instance-level variables for unauthorized users' do
      get api('/admin/ci/variables')

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

  describe 'GET /admin/ci/variables/:key' do
    let!(:variable) { create(:ci_instance_variable) }

    it 'returns instance-level variable details for admins', :aggregate_failures do
      get api("/admin/ci/variables/#{variable.key}", admin)

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response['value']).to eq(variable.value)
      expect(json_response['protected']).to eq(variable.protected?)
      expect(json_response['variable_type']).to eq(variable.variable_type)
    end

    it 'responds with 404 Not Found if requesting non-existing variable' do
      get api('/admin/ci/variables/non_existing_variable', admin)

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

    it 'does not return instance-level variable details for regular users' do
      get api("/admin/ci/variables/#{variable.key}", user)

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

    it 'does not return instance-level variable details for unauthorized users' do
      get api("/admin/ci/variables/#{variable.key}")

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

  describe 'POST /admin/ci/variables' do
    context 'authorized user with proper permissions' do
      let!(:variable) { create(:ci_instance_variable) }

      it 'creates variable for admins', :aggregate_failures do
        expect do
          post api('/admin/ci/variables', admin),
            params: {
              key: 'TEST_VARIABLE_2',
              value: 'PROTECTED_VALUE_2',
              protected: true,
              masked: true,
              raw: true
            }
        end.to change { ::Ci::InstanceVariable.count }.by(1)

        expect(response).to have_gitlab_http_status(:created)
        expect(json_response['key']).to eq('TEST_VARIABLE_2')
        expect(json_response['value']).to eq('PROTECTED_VALUE_2')
        expect(json_response['protected']).to be_truthy
        expect(json_response['masked']).to be_truthy
        expect(json_response['raw']).to be_truthy
        expect(json_response['variable_type']).to eq('env_var')
      end

      it 'masks the new value when logging' do
        masked_params = { 'key' => 'VAR_KEY', 'value' => '[FILTERED]', 'protected' => 'true', 'masked' => 'true' }

        expect(::API::API::LOGGER).to receive(:info).with(include(params: include(masked_params)))

        post api("/admin/ci/variables", user),
          params: { key: 'VAR_KEY', value: 'SENSITIVE', protected: true, masked: true }
      end

      it 'creates variable with optional attributes', :aggregate_failures do
        expect do
          post api('/admin/ci/variables', admin),
            params: {
              variable_type: 'file',
              key: 'TEST_VARIABLE_2',
              value: 'VALUE_2'
            }
        end.to change { ::Ci::InstanceVariable.count }.by(1)

        expect(response).to have_gitlab_http_status(:created)
        expect(json_response['key']).to eq('TEST_VARIABLE_2')
        expect(json_response['value']).to eq('VALUE_2')
        expect(json_response['protected']).to be_falsey
        expect(json_response['masked']).to be_falsey
        expect(json_response['raw']).to be_falsey
        expect(json_response['variable_type']).to eq('file')
      end

      it 'does not allow to duplicate variable key' do
        expect do
          post api('/admin/ci/variables', admin),
            params: { key: variable.key, value: 'VALUE_2' }
        end.not_to change { ::Ci::InstanceVariable.count }

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

      it 'does not allow values above 10,000 characters' do
        too_long_message = <<~MESSAGE.strip
          The value of the provided variable exceeds the 10000 character limit
        MESSAGE

        expect do
          post api('/admin/ci/variables', admin),
            params: { key: 'too_long', value: SecureRandom.hex(10_001) }
        end.not_to change { ::Ci::InstanceVariable.count }

        expect(response).to have_gitlab_http_status(:bad_request)
        expect(json_response).to match('message' =>
          a_hash_including('value' => [too_long_message]))
      end
    end

    context 'authorized user with invalid permissions' do
      it 'does not create variable' do
        post api('/admin/ci/variables', user)

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

    context 'unauthorized user' do
      it 'does not create variable' do
        post api('/admin/ci/variables')

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

  describe 'PUT /admin/ci/variables/:key' do
    let!(:variable) { create(:ci_instance_variable) }

    context 'authorized user with proper permissions' do
      it 'updates variable data', :aggregate_failures do
        put api("/admin/ci/variables/#{variable.key}", admin),
          params: {
            variable_type: 'file',
            value: 'VALUE_1_UP',
            protected: true,
            masked: true,
            raw: true
          }

        expect(response).to have_gitlab_http_status(:ok)
        expect(variable.reload.value).to eq('VALUE_1_UP')
        expect(variable.reload).to be_protected
        expect(json_response['variable_type']).to eq('file')
        expect(json_response['masked']).to be_truthy
        expect(json_response['raw']).to be_truthy
      end

      it 'masks the new value when logging' do
        masked_params = { 'value' => '[FILTERED]', 'protected' => 'true', 'masked' => 'true' }

        expect(::API::API::LOGGER).to receive(:info).with(include(params: include(masked_params)))

        put api("/admin/ci/variables/#{variable.key}", admin),
          params: { value: 'SENSITIVE', protected: true, masked: true }
      end

      it 'responds with 404 Not Found if requesting non-existing variable' do
        put api('/admin/ci/variables/non_existing_variable', admin)

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

    context 'authorized user with invalid permissions' do
      it 'does not update variable' do
        put api("/admin/ci/variables/#{variable.key}", user)

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

    context 'unauthorized user' do
      it 'does not update variable' do
        put api("/admin/ci/variables/#{variable.key}")

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

  describe 'DELETE /admin/ci/variables/:key' do
    let!(:variable) { create(:ci_instance_variable) }

    context 'authorized user with proper permissions' do
      it 'deletes variable' do
        expect do
          delete api("/admin/ci/variables/#{variable.key}", admin)

          expect(response).to have_gitlab_http_status(:no_content)
        end.to change { ::Ci::InstanceVariable.count }.by(-1)
      end

      it 'responds with 404 Not Found if requesting non-existing variable' do
        delete api('/admin/ci/variables/non_existing_variable', admin)

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

    context 'authorized user with invalid permissions' do
      it 'does not delete variable' do
        delete api("/admin/ci/variables/#{variable.key}", user)

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

    context 'unauthorized user' do
      it 'does not delete variable' do
        delete api("/admin/ci/variables/#{variable.key}")

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