From 96b49d7897a489c6af7fe49328f362ab4bb6676e Mon Sep 17 00:00:00 2001
From: Harsha Muralidhar <hmuralidhar@gitlab.com>
Date: Mon, 28 Oct 2024 17:11:29 +0000
Subject: [PATCH] Add group pipeline execution policy E2E spec

---
 .../policy_editor/editor_layout.vue           |   2 +
 .../action/code_block_file_path.vue           |   2 +
 .../action/code_block_strategy_selector.vue   |   1 +
 .../page/group/policies/security_policies.rb  |  76 +++++++
 .../group_pipeline_execution_policy_spec.rb   | 193 ++++++++++++++++++
 5 files changed, 274 insertions(+)
 create mode 100644 qa/qa/ee/page/group/policies/security_policies.rb
 create mode 100644 qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb

diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue
index c85099800bd79..57ac96edd1ae6 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue
@@ -253,6 +253,7 @@ export default {
             >
               <gl-form-input
                 id="policyName"
+                data-testid="policy-name-text"
                 :disabled="hasParsingError"
                 :state="hasValidName || !showValidation"
                 :value="policy.name"
@@ -268,6 +269,7 @@ export default {
             >
               <gl-form-textarea
                 id="policyDescription"
+                data-testid="policy-description-text"
                 :disabled="hasParsingError"
                 :value="policy.description"
                 no-resize
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue
index c378ac7db3f89..b056f9facc7ed 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue
@@ -193,6 +193,7 @@ export default {
         <template #projectSelector>
           <group-projects-dropdown
             class="gl-max-w-20"
+            data-testid="pipeline-execution-project-dropdown"
             :group-full-path="groupProjectsPath"
             :selected="selectedProjectId"
             :multiple="false"
@@ -235,6 +236,7 @@ export default {
           <gl-form-group
             class="gl-mb-0 gl-w-full"
             label-sr-only
+            data-testid="ci-file-path-text"
             :label="$options.i18n.formGroupLabel"
             :optional="false"
             :invalid-feedback="filePathValidationError"
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue
index 59e97730c463e..b5d32a9d510ec 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue
@@ -32,6 +32,7 @@ export default {
 <template>
   <gl-collapsible-listbox
     label-for="file-path"
+    data-testid="strategy-selector-dropdown"
     :items="$options.CUSTOM_STRATEGY_OPTIONS_LISTBOX_ITEMS"
     :toggle-text="toggleText"
     :selected="strategy"
diff --git a/qa/qa/ee/page/group/policies/security_policies.rb b/qa/qa/ee/page/group/policies/security_policies.rb
new file mode 100644
index 0000000000000..d75d17af170b2
--- /dev/null
+++ b/qa/qa/ee/page/group/policies/security_policies.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module QA
+  module EE
+    module Page
+      module Group
+        module Policies
+          class SecurityPolicies < QA::Page::Base
+            view 'ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue' do
+              element 'new-policy-button'
+            end
+
+            view 'ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action' \
+              '/code_block_file_path.vue' do
+              element 'pipeline-execution-project-dropdown'
+              element 'ci-file-path-text'
+            end
+
+            view 'ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue' do
+              element 'policy-name-text'
+              element 'policy-description-text'
+              element 'save-policy'
+            end
+
+            def click_new_policy
+              click_element('new-policy-button')
+              wait_for_requests
+            end
+
+            def click_pipeline_execution_policy
+              click_element('select-policy-pipeline_execution_policy')
+              wait_for_requests
+            end
+
+            def set_policy_name(policy_name)
+              fill_element('policy-name-text', policy_name)
+            end
+
+            def select_strategy(override = false)
+              click_element('strategy-selector-dropdown')
+
+              if override
+                click_element('listbox-item-override_project_ci')
+              else
+                click_element('listbox-item-inject_ci')
+              end
+            end
+
+            def set_ci_file_path(file_path)
+              within_element('ci-file-path-text') do
+                find('[id="file-path"]').set(file_path)
+              end
+            end
+
+            def set_policy_description(description)
+              fill_element('policy-description-text', description)
+            end
+
+            def select_project(project_id)
+              click_element('pipeline-execution-project-dropdown')
+              find_element(project_listbox_item(project_id)).click
+            end
+
+            def save_policy
+              click_element('save-policy')
+            end
+
+            def project_listbox_item(project_id)
+              "listbox-item-gid://gitlab/Project/#{project_id}"
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb b/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb
new file mode 100644
index 0000000000000..b53a456ca0d1b
--- /dev/null
+++ b/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+module QA
+  RSpec.describe 'Govern', :runner, product_group: :security_policies do
+    describe 'Group Pipeline Execution Policy' do
+      let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
+      let(:group) { create(:group) }
+      let(:policy_name) { 'Greyhound' }
+      let(:template_project) do
+        create(:project, name: 'template-project-pipeline-exec-policy',
+          group: group, initialize_with_readme: true)
+      end
+
+      let(:template_project_ci_path) { "policy-ci-#{Faker::Alphanumeric.alphanumeric(number: 4)}.yml" }
+
+      let(:project) do
+        create(:project, name: 'project-test-pipeline-exec-policy', group: group, initialize_with_readme: true)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+          project: project,
+          description: Faker::Lorem.sentence,
+          target_new_branch: false,
+          file_name: Faker::File.unique.file_name,
+          file_content: Faker::Lorem.sentence)
+      end
+
+      let!(:runner) { create(:group_runner, group: group, name: executor, tags: [executor]) }
+
+      let!(:pipeline_exec_policy_ci_file) do
+        create(:commit, project: template_project, commit_message: 'Add policy-ci.yml', actions: [
+          { action: 'create', file_path: template_project_ci_path, content: pipeline_execution_policy_yaml }
+        ])
+      end
+
+      let!(:project_ci_file) do
+        create(:commit, project: project, commit_message: 'Add project .gitlab-ci.yml', actions: [
+          { action: 'create', file_path: '.gitlab-ci.yml', content: project_ci_yaml }
+        ])
+      end
+
+      let(:expected_job_log) { "This job is due to pipeline execution policy for #{group.path}" }
+
+      before do
+        Flow::Login.sign_in
+      end
+
+      after do
+        runner.remove_via_api!
+      end
+
+      it 'executes jobs as per inject strategy',
+        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/499398' do
+        create_pipeline_execution_policy
+        merge_pipeline_execution_policy
+
+        merge_request.visit!
+        Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project)
+        Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success')
+
+        Flow::Pipeline.visit_latest_pipeline
+        aggregate_failures do
+          Page::Project::Pipeline::Show.perform do |pipeline|
+            expect(pipeline).to have_job('pipeline_exec_policy_build_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_pre_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_post_job')
+            expect(pipeline).to have_job('project_job')
+          end
+        end
+
+        project.visit_job('pipeline_exec_policy_build_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_build_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_pre_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_pre_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_post_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_post_job', expected_text: expected_job_log)
+
+        project.visit_job('project_job')
+
+        verify_expected_job_log(job_name: 'project_job', expected_text: 'This is the project job. Colour grey')
+      end
+
+      it 'executes jobs as per override strategy',
+        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/499418' do
+        create_pipeline_execution_policy(override: true)
+        merge_pipeline_execution_policy
+
+        merge_request.visit!
+        Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project)
+        Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success')
+
+        Flow::Pipeline.visit_latest_pipeline
+        aggregate_failures do
+          Page::Project::Pipeline::Show.perform do |pipeline|
+            expect(pipeline).to have_job('pipeline_exec_policy_build_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_pre_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_post_job')
+            expect(pipeline).not_to have_job('project_job')
+          end
+        end
+
+        project.visit_job('pipeline_exec_policy_build_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_build_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_pre_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_pre_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_post_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_post_job', expected_text: expected_job_log)
+      end
+
+      private
+
+      def create_pipeline_execution_policy(override: false)
+        group.visit!
+        Page::Group::Menu.perform(&:go_to_policies)
+
+        EE::Page::Group::Policies::SecurityPolicies.perform do |new_policy_page|
+          new_policy_page.click_new_policy
+          new_policy_page.click_pipeline_execution_policy
+          new_policy_page.set_policy_name(policy_name)
+          new_policy_page.set_policy_description('To test pipeline execution policy')
+          new_policy_page.select_strategy(override) if override
+          new_policy_page.select_project(template_project.id)
+          new_policy_page.set_ci_file_path(template_project_ci_path)
+          new_policy_page.save_policy
+        end
+      end
+
+      def merge_pipeline_execution_policy
+        Support::Waiter.wait_until(message: 'Wait for policy MR page', max_duration: 80) do
+          Page::MergeRequest::Show.perform(&:has_merge_button?)
+        end
+
+        Support::Retrier.retry_on_exception(sleep_interval: 2, message: "Retrying policy merge") do
+          Page::MergeRequest::Show.perform(&:merge!)
+        end
+      end
+
+      def verify_expected_job_log(job_name:, expected_text:)
+        project.visit_job(job_name)
+        Page::Project::Job::Show.perform do |show|
+          expect(show.output).to have_content(expected_text),
+            "Didn't find '#{expected_text}' within #{job_name}'s log:\n#{show.output}."
+        end
+      end
+
+      def pipeline_execution_policy_yaml
+        <<~YAML
+          default:
+            tags: ["#{executor}"]
+
+          pipeline_exec_policy_build_job:
+            stage: build
+            script:
+              - echo "#{expected_job_log}"
+
+          pipeline_exec_policy_pre_job:
+            stage: .pipeline-policy-pre
+            script:
+              - echo "#{expected_job_log}"
+
+          pipeline_exec_policy_post_job:
+            stage: .pipeline-policy-post
+            script:
+              - echo "#{expected_job_log}"
+        YAML
+      end
+
+      def project_ci_yaml
+        <<~YAML
+          stages:
+            - build
+
+          project_job:
+            stage: build
+            tags: ["#{executor}"]
+            script:
+              - echo "This is the project job. Colour grey"
+        YAML
+      end
+    end
+  end
+end
-- 
GitLab