From b56a9513c221623fbb63d0dafa2305606e76d49c Mon Sep 17 00:00:00 2001 From: Andy Schoenen Date: Tue, 21 Oct 2025 16:26:10 +0200 Subject: [PATCH] Add integration test for policy dismissal audit events This test verifies that audit events are created when a merge request with dismissed warn-mode security policies is merged. The test covers the complete flow: 1. Creating a security policy in warn mode 2. Creating an MR with policy violations (mocked) 3. Dismissing the policy violations 4. Merging the MR 5. Verifying audit event creation This integration test validates the functionality described in MR !205857 which adds audit events when merge requests with dismissed security policies are merged. Changelog: added EE: true --- ...policy_dismissal_audit_integration_spec.rb | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 ee/spec/features/merge_requests/policy_dismissal_audit_integration_spec.rb diff --git a/ee/spec/features/merge_requests/policy_dismissal_audit_integration_spec.rb b/ee/spec/features/merge_requests/policy_dismissal_audit_integration_spec.rb new file mode 100644 index 00000000000000..91f0b1bfd99b0f --- /dev/null +++ b/ee/spec/features/merge_requests/policy_dismissal_audit_integration_spec.rb @@ -0,0 +1,216 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Policy dismissal audit event integration', :js, feature_category: :security_policy_management do + include_context 'with security orchestration policy configuration' + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + let_it_be(:policy_management_project) { create(:project, :repository) } + let_it_be(:policy_configuration) do + create(:security_orchestration_policy_configuration, + project: project, + security_policy_management_project: policy_management_project) + end + + let(:approval_policy) do + { + name: 'MR - Security Scan', + description: 'Security Scan', + enabled: true, + enforcement_type: 'warn', + rules: [ + { + type: 'scan_finding', + scanners: ['secret_detection'], + vulnerabilities_allowed: 0, + severity_levels: [], + vulnerability_states: [], + branch_type: 'protected' + } + ], + actions: [ + { + type: 'require_approval', + approvals_required: 1, + role_approvers: ['developer', 'maintainer', 'owner'] + }, + { + type: 'send_bot_message', + enabled: true + } + ], + approval_settings: { + block_branch_modification: false, + prevent_pushing_and_force_pushing: false, + prevent_approval_by_author: false, + prevent_approval_by_commit_author: false, + remove_approvals_with_new_commit: false, + require_password_to_approve: false + }, + fallback_behavior: { + fail: 'open' + } + } + end + + let(:policy_yaml) do + { + approval_policy: [approval_policy] + }.to_yaml + end + + before_all do + project.add_maintainer(user) + policy_management_project.add_maintainer(user) + end + + before do + stub_licensed_features(security_orchestration_policies: true) + stub_feature_flags(security_policy_approval_warn_mode: true) + sign_in(user) + + # Create the security policy + create_policy_commit(policy_yaml) + end + + context 'when merge request has dismissed security policy violations' do + let!(:merge_request) { create_test_merge_request } + let!(:security_policy) { create_security_policy } + let!(:policy_dismissal) { create_policy_dismissal(merge_request, security_policy) } + + it 'creates audit event when MR is merged with dismissed policy' do + # Verify initial state + expect(merge_request.policy_dismissals).to include(policy_dismissal) + expect(policy_dismissal.status).to eq('open') + expect(AuditEvent.where(entity: project)).to be_empty + + # Merge the MR + merge_request.mark_as_mergeable + MergeRequests::MergeService.new(project: project, current_user: user).execute(merge_request) + + # Wait for background jobs to complete + perform_enqueued_jobs + + # Verify policy dismissal was preserved + expect(policy_dismissal.reload.status).to eq('preserved') + + # Verify audit event was created + audit_events = AuditEvent.where(entity: project) + expect(audit_events.count).to eq(1) + + audit_event = audit_events.first + expect(audit_event.details[:custom_message]).to include( + "Merge request !#{merge_request.iid} was merged with violated security policy." + ) + expect(audit_event.author).to eq(policy_dismissal.user) + expect(audit_event.target_type).to eq('Security::Policy') + expect(audit_event.target_id).to eq(security_policy.id) + end + + context 'when policy dismissal is not applicable for all violations' do + let!(:additional_violation) { create_additional_violation(merge_request, security_policy) } + + it 'destroys non-applicable dismissal and does not create audit event' do + # Make dismissal non-applicable by not covering all violation UUIDs + policy_dismissal.update!(security_findings_uuids: ['partial-uuid']) + + expect(policy_dismissal.applicable_for_all_violations?).to be_falsey + + # Merge the MR + merge_request.mark_as_mergeable + MergeRequests::MergeService.new(project: project, current_user: user).execute(merge_request) + + # Wait for background jobs to complete + perform_enqueued_jobs + + # Verify dismissal was destroyed + expect { policy_dismissal.reload }.to raise_error(ActiveRecord::RecordNotFound) + + # Verify no audit event was created + expect(AuditEvent.where(entity: project)).to be_empty + end + end + end + + context 'when merge request has no policy dismissals' do + let!(:merge_request) { create_test_merge_request } + + it 'does not create audit events' do + # Merge the MR + merge_request.mark_as_mergeable + MergeRequests::MergeService.new(project: project, current_user: user).execute(merge_request) + + # Wait for background jobs to complete + perform_enqueued_jobs + + # Verify no audit event was created + expect(AuditEvent.where(entity: project)).to be_empty + end + end + + private + + def create_policy_commit(policy_content) + policy_management_project.repository.create_file( + user, + '.gitlab/security-policies/policy.yml', + policy_content, + message: 'Add security policy', + branch_name: policy_management_project.default_branch + ) + end + + def create_test_merge_request + # Create a simple test MR without actual vulnerabilities + # The policy violations will be mocked in the test setup + branch_name = 'test-branch' + project.repository.create_branch(branch_name, project.default_branch) + + # Add a simple test file + project.repository.create_file( + user, + 'test_file.txt', + "This is a test file for policy dismissal integration test\n", + message: 'Add test file', + branch_name: branch_name + ) + + create(:merge_request, + source_project: project, + target_project: project, + source_branch: branch_name, + target_branch: project.default_branch, + author: user, + title: 'Test MR for policy dismissal') + end + + def create_security_policy + create(:security_policy, + security_orchestration_policy_configuration: policy_configuration, + name: 'MR - Security Scan', + policy_index: 0) + end + + def create_policy_dismissal(merge_request, security_policy) + create(:policy_dismissal, + project: project, + merge_request: merge_request, + security_policy: security_policy, + user: user, + security_findings_uuids: ['test-uuid-1', 'test-uuid-2'], + dismissal_types: [Security::PolicyDismissal::DISMISSAL_TYPES[:policy_false_positive]], + comment: 'Dismissed due to false positive') + end + + def create_additional_violation(merge_request, security_policy) + approval_policy_rule = create(:approval_policy_rule, security_policy: security_policy) + + create(:scan_result_policy_violation, :new_scan_finding, + project: project, + merge_request: merge_request, + approval_policy_rule: approval_policy_rule, + uuids: ['additional-uuid']) + end +end -- GitLab