# frozen_string_literal: true

require 'spec_helper'

RSpec.describe ::API::Admin::Ci::Variables, :aggregate_failures, feature_category: :pipeline_composition do
  let_it_be(:admin) { create(:admin) }
  let_it_be(:user) { create(:user) }
  let_it_be(:variable) { create(:ci_instance_variable) }
  let_it_be(:path) { '/admin/ci/variables' }

  describe 'GET /admin/ci/variables' do
    it_behaves_like 'GET request permissions for admin mode'

    it 'returns instance-level variables for admins' do
      get api(path, admin, admin_mode: true)

      expect(json_response).to be_a(Array)
    end

    it 'does not return instance-level variables for unauthorized users' do
      get api(path, admin_mode: true)

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

  describe 'GET /admin/ci/variables/:key' do
    let_it_be(:path) { "/admin/ci/variables/#{variable.key}" }

    it_behaves_like 'GET request permissions for admin mode'

    it 'returns instance-level variable details for admins' do
      get api(path, admin, admin_mode: true)

      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, admin_mode: true)

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

    it 'does not return instance-level variable details for unauthorized users' do
      get api(path, admin_mode: true)

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

  describe 'POST /admin/ci/variables' do
    it_behaves_like 'POST request permissions for admin mode' do
      let(:params) { { key: 'KEY', value: 'VALUE' } }
    end

    context 'authorized user with proper permissions' do
      it 'creates variable for admins' do
        expect do
          post api(path, admin, admin_mode: true),
            params: {
              key: 'TEST_VARIABLE_2',
              value: 'PROTECTED_VALUE_2',
              protected: true,
              masked: true,
              raw: true
            }
        end.to change { ::Ci::InstanceVariable.count }.by(1)

        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(path, user, admin_mode: true),
          params: { key: 'VAR_KEY', value: 'SENSITIVE', protected: true, masked: true }
      end

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

        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(path, admin, admin_mode: true),
            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(path, admin, admin_mode: true),
            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 'unauthorized user' do
      it 'does not create variable' do
        post api(path, admin_mode: true)

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

  describe 'PUT /admin/ci/variables/:key' do
    let_it_be(:path) { "/admin/ci/variables/#{variable.key}" }
    let_it_be(:params) do
      {
        variable_type: 'file',
        value: 'VALUE_1_UP',
        protected: true,
        masked: true,
        raw: true
      }
    end

    it_behaves_like 'PUT request permissions for admin mode'

    context 'authorized user with proper permissions' do
      it 'updates variable data' do
        put api(path, admin, admin_mode: true), params: params

        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(path, admin, admin_mode: true),
          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, admin_mode: true)

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

    context 'unauthorized user' do
      it 'does not update variable' do
        put api(path, admin_mode: true)

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

  describe 'DELETE /admin/ci/variables/:key' do
    let_it_be(:path) { "/admin/ci/variables/#{variable.key}" }

    it_behaves_like 'DELETE request permissions for admin mode'

    context 'authorized user with proper permissions' do
      it 'deletes variable' do
        expect do
          delete api(path, admin, admin_mode: true)
        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, admin_mode: true)

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

    context 'unauthorized user' do
      it 'does not delete variable' do
        delete api(path, admin_mode: true)

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