520 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			520 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| require 'spec_helper'
 | |
| 
 | |
| RSpec.describe Gitlab::Ci::Variables::Builder do
 | |
|   let_it_be(:group) { create(:group) }
 | |
|   let_it_be(:project) { create(:project, :repository, namespace: group) }
 | |
|   let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) }
 | |
|   let_it_be(:user) { create(:user) }
 | |
|   let_it_be_with_reload(:job) do
 | |
|     create(:ci_build,
 | |
|       pipeline: pipeline,
 | |
|       user: user,
 | |
|       yaml_variables: [{ key: 'YAML_VARIABLE', value: 'value' }]
 | |
|     )
 | |
|   end
 | |
| 
 | |
|   let(:builder) { described_class.new(pipeline) }
 | |
| 
 | |
|   describe '#scoped_variables' do
 | |
|     let(:environment) { job.expanded_environment_name }
 | |
|     let(:dependencies) { true }
 | |
|     let(:predefined_variables) do
 | |
|       [
 | |
|         { key: 'CI_JOB_NAME',
 | |
|           value: job.name },
 | |
|         { key: 'CI_JOB_STAGE',
 | |
|           value: job.stage },
 | |
|         { key: 'CI_NODE_TOTAL',
 | |
|           value: '1' },
 | |
|         { key: 'CI_BUILD_NAME',
 | |
|           value: job.name },
 | |
|         { key: 'CI_BUILD_STAGE',
 | |
|           value: job.stage },
 | |
|         { key: 'CI',
 | |
|           value: 'true' },
 | |
|         { key: 'GITLAB_CI',
 | |
|           value: 'true' },
 | |
|         { key: 'CI_SERVER_URL',
 | |
|           value: Gitlab.config.gitlab.url },
 | |
|         { key: 'CI_SERVER_HOST',
 | |
|           value: Gitlab.config.gitlab.host },
 | |
|         { key: 'CI_SERVER_PORT',
 | |
|           value: Gitlab.config.gitlab.port.to_s },
 | |
|         { key: 'CI_SERVER_PROTOCOL',
 | |
|           value: Gitlab.config.gitlab.protocol },
 | |
|         { key: 'CI_SERVER_NAME',
 | |
|           value: 'GitLab' },
 | |
|         { key: 'CI_SERVER_VERSION',
 | |
|           value: Gitlab::VERSION },
 | |
|         { key: 'CI_SERVER_VERSION_MAJOR',
 | |
|           value: Gitlab.version_info.major.to_s },
 | |
|         { key: 'CI_SERVER_VERSION_MINOR',
 | |
|           value: Gitlab.version_info.minor.to_s },
 | |
|         { key: 'CI_SERVER_VERSION_PATCH',
 | |
|           value: Gitlab.version_info.patch.to_s },
 | |
|         { key: 'CI_SERVER_REVISION',
 | |
|           value: Gitlab.revision },
 | |
|         { key: 'GITLAB_FEATURES',
 | |
|           value: project.licensed_features.join(',') },
 | |
|         { key: 'CI_PROJECT_ID',
 | |
|           value: project.id.to_s },
 | |
|         { key: 'CI_PROJECT_NAME',
 | |
|           value: project.path },
 | |
|         { key: 'CI_PROJECT_TITLE',
 | |
|           value: project.title },
 | |
|         { key: 'CI_PROJECT_PATH',
 | |
|           value: project.full_path },
 | |
|         { key: 'CI_PROJECT_PATH_SLUG',
 | |
|           value: project.full_path_slug },
 | |
|         { key: 'CI_PROJECT_NAMESPACE',
 | |
|           value: project.namespace.full_path },
 | |
|         { key: 'CI_PROJECT_ROOT_NAMESPACE',
 | |
|           value: project.namespace.root_ancestor.path },
 | |
|         { key: 'CI_PROJECT_URL',
 | |
|           value: project.web_url },
 | |
|         { key: 'CI_PROJECT_VISIBILITY',
 | |
|           value: "private" },
 | |
|         { key: 'CI_PROJECT_REPOSITORY_LANGUAGES',
 | |
|           value: project.repository_languages.map(&:name).join(',').downcase },
 | |
|         { key: 'CI_PROJECT_CLASSIFICATION_LABEL',
 | |
|           value: project.external_authorization_classification_label },
 | |
|         { key: 'CI_DEFAULT_BRANCH',
 | |
|           value: project.default_branch },
 | |
|         { key: 'CI_CONFIG_PATH',
 | |
|           value: project.ci_config_path_or_default },
 | |
|         { key: 'CI_PAGES_DOMAIN',
 | |
|           value: Gitlab.config.pages.host },
 | |
|         { key: 'CI_PAGES_URL',
 | |
|           value: project.pages_url },
 | |
|         { key: 'CI_API_V4_URL',
 | |
|           value: API::Helpers::Version.new('v4').root_url },
 | |
|         { key: 'CI_PIPELINE_IID',
 | |
|           value: pipeline.iid.to_s },
 | |
|         { key: 'CI_PIPELINE_SOURCE',
 | |
|           value: pipeline.source },
 | |
|         { key: 'CI_PIPELINE_CREATED_AT',
 | |
|           value: pipeline.created_at.iso8601 },
 | |
|         { key: 'CI_COMMIT_SHA',
 | |
|           value: job.sha },
 | |
|         { key: 'CI_COMMIT_SHORT_SHA',
 | |
|           value: job.short_sha },
 | |
|         { key: 'CI_COMMIT_BEFORE_SHA',
 | |
|           value: job.before_sha },
 | |
|         { key: 'CI_COMMIT_REF_NAME',
 | |
|           value: job.ref },
 | |
|         { key: 'CI_COMMIT_REF_SLUG',
 | |
|           value: job.ref_slug },
 | |
|         { key: 'CI_COMMIT_BRANCH',
 | |
|           value: job.ref },
 | |
|         { key: 'CI_COMMIT_MESSAGE',
 | |
|           value: pipeline.git_commit_message },
 | |
|         { key: 'CI_COMMIT_TITLE',
 | |
|           value: pipeline.git_commit_title },
 | |
|         { key: 'CI_COMMIT_DESCRIPTION',
 | |
|           value: pipeline.git_commit_description },
 | |
|         { key: 'CI_COMMIT_REF_PROTECTED',
 | |
|           value: (!!pipeline.protected_ref?).to_s },
 | |
|         { key: 'CI_COMMIT_TIMESTAMP',
 | |
|           value: pipeline.git_commit_timestamp },
 | |
|         { key: 'CI_COMMIT_AUTHOR',
 | |
|           value: pipeline.git_author_full_text },
 | |
|         { key: 'CI_BUILD_REF',
 | |
|           value: job.sha },
 | |
|         { key: 'CI_BUILD_BEFORE_SHA',
 | |
|           value: job.before_sha },
 | |
|         { key: 'CI_BUILD_REF_NAME',
 | |
|           value: job.ref },
 | |
|         { key: 'CI_BUILD_REF_SLUG',
 | |
|           value: job.ref_slug },
 | |
|         { key: 'YAML_VARIABLE',
 | |
|           value: 'value' },
 | |
|         { key: 'GITLAB_USER_ID',
 | |
|           value: user.id.to_s },
 | |
|         { key: 'GITLAB_USER_EMAIL',
 | |
|          value: user.email },
 | |
|         { key: 'GITLAB_USER_LOGIN',
 | |
|          value: user.username },
 | |
|         { key: 'GITLAB_USER_NAME',
 | |
|          value: user.name }
 | |
|       ].map { |var| var.merge(public: true, masked: false) }
 | |
|     end
 | |
| 
 | |
|     subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) }
 | |
| 
 | |
|     it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) }
 | |
| 
 | |
|     it { expect(subject.to_runner_variables).to eq(predefined_variables) }
 | |
| 
 | |
|     context 'variables ordering' do
 | |
|       def var(name, value)
 | |
|         { key: name, value: value.to_s, public: true, masked: false }
 | |
|       end
 | |
| 
 | |
|       before do
 | |
|         allow(builder).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] }
 | |
|         allow(pipeline.project).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] }
 | |
|         allow(pipeline).to receive(:predefined_variables) { [var('C', 3), var('D', 3)] }
 | |
|         allow(job).to receive(:runner) { double(predefined_variables: [var('D', 4), var('E', 4)]) }
 | |
|         allow(builder).to receive(:kubernetes_variables) { [var('E', 5), var('F', 5)] }
 | |
|         allow(builder).to receive(:deployment_variables) { [var('F', 6), var('G', 6)] }
 | |
|         allow(job).to receive(:yaml_variables) { [var('G', 7), var('H', 7)] }
 | |
|         allow(builder).to receive(:user_variables) { [var('H', 8), var('I', 8)] }
 | |
|         allow(job).to receive(:dependency_variables) { [var('I', 9), var('J', 9)] }
 | |
|         allow(builder).to receive(:secret_instance_variables) { [var('J', 10), var('K', 10)] }
 | |
|         allow(builder).to receive(:secret_group_variables) { [var('K', 11), var('L', 11)] }
 | |
|         allow(builder).to receive(:secret_project_variables) { [var('L', 12), var('M', 12)] }
 | |
|         allow(job).to receive(:trigger_request) { double(user_variables: [var('M', 13), var('N', 13)]) }
 | |
|         allow(pipeline).to receive(:variables) { [var('N', 14), var('O', 14)] }
 | |
|         allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('O', 15), var('P', 15)]) }
 | |
|       end
 | |
| 
 | |
|       it 'returns variables in order depending on resource hierarchy' do
 | |
|         expect(subject.to_runner_variables).to eq(
 | |
|           [var('A', 1), var('B', 1),
 | |
|            var('B', 2), var('C', 2),
 | |
|            var('C', 3), var('D', 3),
 | |
|            var('D', 4), var('E', 4),
 | |
|            var('E', 5), var('F', 5),
 | |
|            var('F', 6), var('G', 6),
 | |
|            var('G', 7), var('H', 7),
 | |
|            var('H', 8), var('I', 8),
 | |
|            var('I', 9), var('J', 9),
 | |
|            var('J', 10), var('K', 10),
 | |
|            var('K', 11), var('L', 11),
 | |
|            var('L', 12), var('M', 12),
 | |
|            var('M', 13), var('N', 13),
 | |
|            var('N', 14), var('O', 14),
 | |
|            var('O', 15), var('P', 15)])
 | |
|       end
 | |
| 
 | |
|       it 'overrides duplicate keys depending on resource hierarchy' do
 | |
|         expect(subject.to_hash).to match(
 | |
|           'A' => '1', 'B' => '2',
 | |
|           'C' => '3', 'D' => '4',
 | |
|           'E' => '5', 'F' => '6',
 | |
|           'G' => '7', 'H' => '8',
 | |
|           'I' => '9', 'J' => '10',
 | |
|           'K' => '11', 'L' => '12',
 | |
|           'M' => '13', 'N' => '14',
 | |
|           'O' => '15', 'P' => '15')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '#user_variables' do
 | |
|     context 'with user' do
 | |
|       subject { builder.user_variables(user).to_hash }
 | |
| 
 | |
|       let(:expected_variables) do
 | |
|         {
 | |
|           'GITLAB_USER_EMAIL' => user.email,
 | |
|           'GITLAB_USER_ID' => user.id.to_s,
 | |
|           'GITLAB_USER_LOGIN' => user.username,
 | |
|           'GITLAB_USER_NAME' => user.name
 | |
|         }
 | |
|       end
 | |
| 
 | |
|       it { is_expected.to eq(expected_variables) }
 | |
|     end
 | |
| 
 | |
|     context 'without user' do
 | |
|       subject { builder.user_variables(nil).to_hash }
 | |
| 
 | |
|       it { is_expected.to be_empty }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '#kubernetes_variables' do
 | |
|     let(:service) { double(execute: template) }
 | |
|     let(:template) { double(to_yaml: 'example-kubeconfig', valid?: template_valid) }
 | |
|     let(:template_valid) { true }
 | |
| 
 | |
|     subject { builder.kubernetes_variables(job) }
 | |
| 
 | |
|     before do
 | |
|       allow(Ci::GenerateKubeconfigService).to receive(:new).with(job).and_return(service)
 | |
|     end
 | |
| 
 | |
|     it { is_expected.to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) }
 | |
| 
 | |
|     context 'generated config is invalid' do
 | |
|       let(:template_valid) { false }
 | |
| 
 | |
|       it { is_expected.not_to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '#deployment_variables' do
 | |
|     let(:environment) { 'production' }
 | |
|     let(:kubernetes_namespace) { 'namespace' }
 | |
|     let(:project_variables) { double }
 | |
| 
 | |
|     subject { builder.deployment_variables(environment: environment, job: job) }
 | |
| 
 | |
|     before do
 | |
|       allow(job).to receive(:expanded_kubernetes_namespace)
 | |
|         .and_return(kubernetes_namespace)
 | |
| 
 | |
|       allow(project).to receive(:deployment_variables)
 | |
|         .with(environment: environment, kubernetes_namespace: kubernetes_namespace)
 | |
|         .and_return(project_variables)
 | |
|     end
 | |
| 
 | |
|     context 'environment is nil' do
 | |
|       let(:environment) { nil }
 | |
| 
 | |
|       it { is_expected.to be_empty }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   shared_examples "secret CI variables" do
 | |
|     context 'when ref is branch' do
 | |
|       context 'when ref is protected' do
 | |
|         before do
 | |
|           create(:protected_branch, :developers_can_merge, name: job.ref, project: project)
 | |
|         end
 | |
| 
 | |
|         it { is_expected.to contain_exactly(protected_variable_item, unprotected_variable_item) }
 | |
|       end
 | |
| 
 | |
|       context 'when ref is not protected' do
 | |
|         it { is_expected.to contain_exactly(unprotected_variable_item) }
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'when ref is tag' do
 | |
|       before do
 | |
|         job.update!(ref: 'v1.1.0', tag: true)
 | |
|         pipeline.update!(ref: 'v1.1.0', tag: true)
 | |
|       end
 | |
| 
 | |
|       context 'when ref is protected' do
 | |
|         before do
 | |
|           create(:protected_tag, project: project, name: 'v*')
 | |
|         end
 | |
| 
 | |
|         it { is_expected.to contain_exactly(protected_variable_item, unprotected_variable_item) }
 | |
|       end
 | |
| 
 | |
|       context 'when ref is not protected' do
 | |
|         it { is_expected.to contain_exactly(unprotected_variable_item) }
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'when ref is merge request' do
 | |
|       let_it_be(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) }
 | |
|       let_it_be(:pipeline) { merge_request.pipelines_for_merge_request.first }
 | |
|       let_it_be(:job) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline) }
 | |
| 
 | |
|       context 'when ref is protected' do
 | |
|         before do
 | |
|           create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project)
 | |
|         end
 | |
| 
 | |
|         it 'does not return protected variables as it is not supported for merge request pipelines' do
 | |
|           is_expected.to contain_exactly(unprotected_variable_item)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       context 'when ref is not protected' do
 | |
|         it { is_expected.to contain_exactly(unprotected_variable_item) }
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '#secret_instance_variables' do
 | |
|     subject { builder.secret_instance_variables }
 | |
| 
 | |
|     let_it_be(:protected_variable) { create(:ci_instance_variable, protected: true) }
 | |
|     let_it_be(:unprotected_variable) { create(:ci_instance_variable, protected: false) }
 | |
| 
 | |
|     let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) }
 | |
|     let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) }
 | |
| 
 | |
|     include_examples "secret CI variables"
 | |
|   end
 | |
| 
 | |
|   describe '#secret_group_variables' do
 | |
|     subject { builder.secret_group_variables(ref: job.git_ref, environment: job.expanded_environment_name) }
 | |
| 
 | |
|     let_it_be(:protected_variable) { create(:ci_group_variable, protected: true, group: group) }
 | |
|     let_it_be(:unprotected_variable) { create(:ci_group_variable, protected: false, group: group) }
 | |
| 
 | |
|     context 'with ci_variables_builder_memoize_secret_variables disabled' do
 | |
|       before do
 | |
|         stub_feature_flags(ci_variables_builder_memoize_secret_variables: false)
 | |
|       end
 | |
| 
 | |
|       let(:protected_variable_item) { protected_variable }
 | |
|       let(:unprotected_variable_item) { unprotected_variable }
 | |
| 
 | |
|       include_examples "secret CI variables"
 | |
|     end
 | |
| 
 | |
|     context 'with ci_variables_builder_memoize_secret_variables enabled' do
 | |
|       before do
 | |
|         stub_feature_flags(ci_variables_builder_memoize_secret_variables: true)
 | |
|       end
 | |
| 
 | |
|       let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) }
 | |
|       let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) }
 | |
| 
 | |
|       include_examples "secret CI variables"
 | |
| 
 | |
|       context 'variables memoization' do
 | |
|         let_it_be(:scoped_variable) { create(:ci_group_variable, group: group, environment_scope: 'scoped') }
 | |
| 
 | |
|         let(:ref) { job.git_ref }
 | |
|         let(:environment) { job.expanded_environment_name }
 | |
|         let(:scoped_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(scoped_variable) }
 | |
| 
 | |
|         context 'with protected environments' do
 | |
|           it 'memoizes the result by environment' do
 | |
|             expect(pipeline.project)
 | |
|               .to receive(:protected_for?)
 | |
|               .with(pipeline.jobs_git_ref)
 | |
|               .once.and_return(true)
 | |
| 
 | |
|             expect_next_instance_of(described_class::Group) do |group_variables_builder|
 | |
|               expect(group_variables_builder)
 | |
|                 .to receive(:secret_variables)
 | |
|                 .with(environment: 'production', protected_ref: true)
 | |
|                 .once
 | |
|                 .and_call_original
 | |
|             end
 | |
| 
 | |
|             2.times do
 | |
|               expect(builder.secret_group_variables(ref: ref, environment: 'production'))
 | |
|                 .to contain_exactly(unprotected_variable_item, protected_variable_item)
 | |
|             end
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         context 'with unprotected environments' do
 | |
|           it 'memoizes the result by environment' do
 | |
|             expect(pipeline.project)
 | |
|               .to receive(:protected_for?)
 | |
|               .with(pipeline.jobs_git_ref)
 | |
|               .once.and_return(false)
 | |
| 
 | |
|             expect_next_instance_of(described_class::Group) do |group_variables_builder|
 | |
|               expect(group_variables_builder)
 | |
|                 .to receive(:secret_variables)
 | |
|                 .with(environment: nil, protected_ref: false)
 | |
|                 .once
 | |
|                 .and_call_original
 | |
| 
 | |
|               expect(group_variables_builder)
 | |
|                 .to receive(:secret_variables)
 | |
|                 .with(environment: 'scoped', protected_ref: false)
 | |
|                 .once
 | |
|                 .and_call_original
 | |
|             end
 | |
| 
 | |
|             2.times do
 | |
|               expect(builder.secret_group_variables(ref: 'other', environment: nil))
 | |
|                 .to contain_exactly(unprotected_variable_item)
 | |
| 
 | |
|               expect(builder.secret_group_variables(ref: 'other', environment: 'scoped'))
 | |
|                 .to contain_exactly(unprotected_variable_item, scoped_variable_item)
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '#secret_project_variables' do
 | |
|     let_it_be(:protected_variable) { create(:ci_variable, protected: true, project: project) }
 | |
|     let_it_be(:unprotected_variable) { create(:ci_variable, protected: false, project: project) }
 | |
| 
 | |
|     let(:ref) { job.git_ref }
 | |
|     let(:environment) { job.expanded_environment_name }
 | |
| 
 | |
|     subject { builder.secret_project_variables(ref: ref, environment: environment) }
 | |
| 
 | |
|     context 'with ci_variables_builder_memoize_secret_variables disabled' do
 | |
|       before do
 | |
|         stub_feature_flags(ci_variables_builder_memoize_secret_variables: false)
 | |
|       end
 | |
| 
 | |
|       let(:protected_variable_item) { protected_variable }
 | |
|       let(:unprotected_variable_item) { unprotected_variable }
 | |
| 
 | |
|       include_examples "secret CI variables"
 | |
|     end
 | |
| 
 | |
|     context 'with ci_variables_builder_memoize_secret_variables enabled' do
 | |
|       before do
 | |
|         stub_feature_flags(ci_variables_builder_memoize_secret_variables: true)
 | |
|       end
 | |
| 
 | |
|       let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) }
 | |
|       let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) }
 | |
| 
 | |
|       include_examples "secret CI variables"
 | |
| 
 | |
|       context 'variables memoization' do
 | |
|         let_it_be(:scoped_variable) { create(:ci_variable, project: project, environment_scope: 'scoped') }
 | |
| 
 | |
|         let(:scoped_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(scoped_variable) }
 | |
| 
 | |
|         context 'with protected environments' do
 | |
|           it 'memoizes the result by environment' do
 | |
|             expect(pipeline.project)
 | |
|               .to receive(:protected_for?)
 | |
|               .with(pipeline.jobs_git_ref)
 | |
|               .once.and_return(true)
 | |
| 
 | |
|             expect_next_instance_of(described_class::Project) do |project_variables_builder|
 | |
|               expect(project_variables_builder)
 | |
|                 .to receive(:secret_variables)
 | |
|                 .with(environment: 'production', protected_ref: true)
 | |
|                 .once
 | |
|                 .and_call_original
 | |
|             end
 | |
| 
 | |
|             2.times do
 | |
|               expect(builder.secret_project_variables(ref: ref, environment: 'production'))
 | |
|                 .to contain_exactly(unprotected_variable_item, protected_variable_item)
 | |
|             end
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         context 'with unprotected environments' do
 | |
|           it 'memoizes the result by environment' do
 | |
|             expect(pipeline.project)
 | |
|               .to receive(:protected_for?)
 | |
|               .with(pipeline.jobs_git_ref)
 | |
|               .once.and_return(false)
 | |
| 
 | |
|             expect_next_instance_of(described_class::Project) do |project_variables_builder|
 | |
|               expect(project_variables_builder)
 | |
|                 .to receive(:secret_variables)
 | |
|                 .with(environment: nil, protected_ref: false)
 | |
|                 .once
 | |
|                 .and_call_original
 | |
| 
 | |
|               expect(project_variables_builder)
 | |
|                 .to receive(:secret_variables)
 | |
|                 .with(environment: 'scoped', protected_ref: false)
 | |
|                 .once
 | |
|                 .and_call_original
 | |
|             end
 | |
| 
 | |
|             2.times do
 | |
|               expect(builder.secret_project_variables(ref: 'other', environment: nil))
 | |
|                 .to contain_exactly(unprotected_variable_item)
 | |
| 
 | |
|               expect(builder.secret_project_variables(ref: 'other', environment: 'scoped'))
 | |
|                 .to contain_exactly(unprotected_variable_item, scoped_variable_item)
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |