From 8982358a044d3581ba2ad30d210a408bffe38b21 Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Fri, 25 Apr 2025 14:18:26 +0200 Subject: [PATCH 01/12] Add audit event for external status check update Changelog: added EE: true --- .../responses/create_service.rb | 63 +++++++++++ .../types/status_check_response_update.yml | 10 ++ ee/lib/api/status_checks.rb | 21 +++- .../responses/create_service_spec.rb | 107 ++++++++++++++++++ 4 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 ee/app/services/external_status_checks/responses/create_service.rb create mode 100644 ee/config/audit_events/types/status_check_response_update.yml create mode 100644 ee/spec/services/external_status_checks/responses/create_service_spec.rb diff --git a/ee/app/services/external_status_checks/responses/create_service.rb b/ee/app/services/external_status_checks/responses/create_service.rb new file mode 100644 index 00000000000000..3ac88a767317ad --- /dev/null +++ b/ee/app/services/external_status_checks/responses/create_service.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module ExternalStatusChecks + module Responses + class CreateService < BaseContainerService + def execute + unless current_user.can?(:provide_status_check_response, merge_request) + return ServiceResponse.error(message: 'Not Found', http_status: :not_found) + end + + response = merge_request.status_check_responses.new( + external_status_check: external_status_check, + status: status, + sha: sha + ) + + if response.save + log_audit_event(response) + ServiceResponse.success(payload: { status_check_response: response }) + else + ServiceResponse.error(message: response.errors.full_messages.to_sentence, http_status: :bad_request) + end + end + + private + + attr_reader :current_user, :project + + def status + params[:status] + end + + def sha + params[:sha] + end + + def merge_request + params[:merge_request] + end + + def external_status_check + params[:external_status_check] + end + + def log_audit_event(response) + ::Gitlab::Audit::Auditor.audit( + name: 'status_check_response_update', + author: current_user, + scope: project, + target: project, + message: "Updated response for status check #{external_status_check.name} to #{status}", + additional_details: { + external_status_check_id: external_status_check.id, + external_status_check_name: external_status_check.name, + status: status, + merge_request_id: merge_request.id, + merge_request_iid: merge_request.iid + } + ) + end + end + end +end diff --git a/ee/config/audit_events/types/status_check_response_update.yml b/ee/config/audit_events/types/status_check_response_update.yml new file mode 100644 index 00000000000000..acf0d771abbb8f --- /dev/null +++ b/ee/config/audit_events/types/status_check_response_update.yml @@ -0,0 +1,10 @@ +--- +name: status_check_response_update +description: A user updates external status check response for merge request +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/413535 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926 +feature_category: security_policy_management +milestone: '18.0' +saved_to_database: true +streamed: true +scope: [Project] diff --git a/ee/lib/api/status_checks.rb b/ee/lib/api/status_checks.rb index 5c400cef4ddf5f..7e2996cc730428 100644 --- a/ee/lib/api/status_checks.rb +++ b/ee/lib/api/status_checks.rb @@ -139,16 +139,27 @@ def check_feature_enabled! end post 'status_check_responses' do merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) - status_check = merge_request.project.external_status_checks.find(params[:external_status_check_id]) + not_found! unless status_check check_sha_param!(params, merge_request) - not_found! unless current_user.can?(:provide_status_check_response, merge_request) - - approval = merge_request.status_check_responses.create!(external_status_check: status_check, sha: params[:sha], status: params[:status]) + result = ::ExternalStatusChecks::Responses::CreateService.new( + container: merge_request.project, + current_user: current_user, + params: { + merge_request: merge_request, + external_status_check: status_check, + sha: params[:sha], + status: params[:status] + } + ).execute - present(approval, with: Entities::MergeRequests::StatusCheckResponse) + if result.success? + present result.payload[:status_check_response], with: Entities::MergeRequests::StatusCheckResponse + else + render_api_error!(result.payload[:errors], result.http_status) + end end segment 'status_checks' do diff --git a/ee/spec/services/external_status_checks/responses/create_service_spec.rb b/ee/spec/services/external_status_checks/responses/create_service_spec.rb new file mode 100644 index 00000000000000..ddecab3ca7b512 --- /dev/null +++ b/ee/spec/services/external_status_checks/responses/create_service_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ExternalStatusChecks::Responses::CreateService, feature_category: :security_policy_management do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let_it_be(:external_status_check) { create(:external_status_check, project: project) } + let_it_be(:user) { create(:user) } + let_it_be(:sha) { merge_request.diff_head_sha } + let_it_be(:status) { 'passed' } + + let(:service) do + described_class.new( + container: project, + current_user: user, + params: { + external_status_check: external_status_check, + merge_request: merge_request, + status: status, + sha: sha + }) + end + + describe '#execute' do + subject { service.execute } + + context 'when feature is not licensed' do + before do + stub_licensed_features(external_status_checks: false) + end + + it 'returns error response with not_found status' do + expect(subject).to be_error + expect(subject.http_status).to eq(:not_found) + end + end + + context 'when feature is not licensed' do + before do + stub_licensed_features(external_status_checks: true) + end + + context 'when user does not have permission to provide status check response' do + it 'returns error response with not_found status' do + expect(subject).to be_error + expect(subject.http_status).to eq(:not_found) + end + end + + context 'when user has permission to provide status check response' do + before do + project.add_developer(user) + end + + context 'when status check response is saved successfully' do + it 'creates a new status check response' do + expect { subject }.to change { merge_request.status_check_responses.count }.by(1) + + subject + + created_response = merge_request.status_check_responses.last + + expect(created_response.external_status_check).to eq(external_status_check) + expect(created_response.status).to eq('passed') + expect(created_response.sha).to eq(sha) + end + + it 'logs an audit event and returns success response' do + expect(::Gitlab::Audit::Auditor).to receive(:audit).with( + name: 'status_check_response_update', + author: user, + scope: project, + target: project, + message: "Updated response for status check #{external_status_check.name} to passed", + additional_details: { + external_status_check_id: external_status_check.id, + external_status_check_name: external_status_check.name, + status: 'passed', + merge_request_id: merge_request.id, + merge_request_iid: merge_request.iid + } + ) + + response = subject + + expect(response).to be_success + end + end + + context 'when status check response fails to save' do + let(:sha) { nil } + + it 'returns error response with validation errors and does not log audit event' do + expect(::Gitlab::Audit::Auditor).not_to receive(:audit) + + response = subject + + expect(response).to be_error + expect(response.message).to eq("Sha can't be blank") + expect(response.http_status).to eq(:bad_request) + end + end + end + end + end +end -- GitLab From b54213dd0f8345a5f29e43e1fb2347608fe23c0e Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Wed, 30 Apr 2025 23:35:37 +0200 Subject: [PATCH 02/12] Fix failing specs --- doc/user/compliance/audit_event_types.md | 1 + .../responses/create_service.rb | 11 ++++--- ee/lib/api/status_checks.rb | 2 +- .../responses/create_service_spec.rb | 32 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 9bec0fcd36d6a0..df25e7f7e59477 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -542,6 +542,7 @@ Audit event types belong to the following product categories. | [`merge_request_merged_with_policy_violations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195775) | A merge request merged with security policy violations | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549813) | Project | | [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project | | [`policy_violations_resolved`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violations are resolved in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549812) | Project | +| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926) | A user updates external status check response for merge request | {{< icon name="check-circle" >}} Yes | GitLab [18.0](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | ### Security testing configuration diff --git a/ee/app/services/external_status_checks/responses/create_service.rb b/ee/app/services/external_status_checks/responses/create_service.rb index 3ac88a767317ad..91c5df6e9025ba 100644 --- a/ee/app/services/external_status_checks/responses/create_service.rb +++ b/ee/app/services/external_status_checks/responses/create_service.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -module ExternalStatusChecks +module ExternalStatusChecks # rubocop:disable Gitlab/BoundedContexts -- TODO module Responses class CreateService < BaseContainerService def execute unless current_user.can?(:provide_status_check_response, merge_request) - return ServiceResponse.error(message: 'Not Found', http_status: :not_found) + return ServiceResponse.error(message: 'Not Found', reason: :not_found) end response = merge_request.status_check_responses.new( @@ -15,10 +15,11 @@ def execute ) if response.save - log_audit_event(response) + log_audit_event + ServiceResponse.success(payload: { status_check_response: response }) else - ServiceResponse.error(message: response.errors.full_messages.to_sentence, http_status: :bad_request) + ServiceResponse.error(message: response.errors.full_messages.to_sentence, reason: :bad_request) end end @@ -42,7 +43,7 @@ def external_status_check params[:external_status_check] end - def log_audit_event(response) + def log_audit_event ::Gitlab::Audit::Auditor.audit( name: 'status_check_response_update', author: current_user, diff --git a/ee/lib/api/status_checks.rb b/ee/lib/api/status_checks.rb index 7e2996cc730428..66b807ac59127a 100644 --- a/ee/lib/api/status_checks.rb +++ b/ee/lib/api/status_checks.rb @@ -158,7 +158,7 @@ def check_feature_enabled! if result.success? present result.payload[:status_check_response], with: Entities::MergeRequests::StatusCheckResponse else - render_api_error!(result.payload[:errors], result.http_status) + render_api_error!(result.payload[:errors], result.reason) end end diff --git a/ee/spec/services/external_status_checks/responses/create_service_spec.rb b/ee/spec/services/external_status_checks/responses/create_service_spec.rb index ddecab3ca7b512..b25031ee92b995 100644 --- a/ee/spec/services/external_status_checks/responses/create_service_spec.rb +++ b/ee/spec/services/external_status_checks/responses/create_service_spec.rb @@ -23,41 +23,39 @@ end describe '#execute' do - subject { service.execute } + subject(:service_response) { service.execute } - context 'when feature is not licensed' do + context 'when external status checks feature is disabled' do before do stub_licensed_features(external_status_checks: false) end it 'returns error response with not_found status' do - expect(subject).to be_error - expect(subject.http_status).to eq(:not_found) + expect(service_response).to be_error + expect(service_response.reason).to eq(:not_found) end end - context 'when feature is not licensed' do + context 'when external status checks feature is enabled' do before do stub_licensed_features(external_status_checks: true) end context 'when user does not have permission to provide status check response' do it 'returns error response with not_found status' do - expect(subject).to be_error - expect(subject.http_status).to eq(:not_found) + expect(service_response).to be_error + expect(service_response.reason).to eq(:not_found) end end context 'when user has permission to provide status check response' do - before do + before_all do project.add_developer(user) end context 'when status check response is saved successfully' do it 'creates a new status check response' do - expect { subject }.to change { merge_request.status_check_responses.count }.by(1) - - subject + expect { service_response }.to change { merge_request.status_check_responses.count }.by(1) created_response = merge_request.status_check_responses.last @@ -82,9 +80,9 @@ } ) - response = subject + result = service_response - expect(response).to be_success + expect(result).to be_success end end @@ -94,11 +92,11 @@ it 'returns error response with validation errors and does not log audit event' do expect(::Gitlab::Audit::Auditor).not_to receive(:audit) - response = subject + result = service_response - expect(response).to be_error - expect(response.message).to eq("Sha can't be blank") - expect(response.http_status).to eq(:bad_request) + expect(result).to be_error + expect(result.message).to eq("Sha can't be blank") + expect(result.reason).to eq(:bad_request) end end end -- GitLab From 1beda450449966eb304c286fe57d99c0fcd2ffd6 Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Thu, 29 May 2025 16:50:15 +0200 Subject: [PATCH 03/12] Address MR comments --- doc/user/compliance/audit_event_types.md | 1 + .../external_status_checks/responses/create_service.rb | 2 +- ee/config/audit_events/types/status_check_response_update.yml | 4 ++-- .../external_status_checks/responses/create_service_spec.rb | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index df25e7f7e59477..b8d43cd4cdc872 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -543,6 +543,7 @@ Audit event types belong to the following product categories. | [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project | | [`policy_violations_resolved`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violations are resolved in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549812) | Project | | [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926) | A user updates external status check response for merge request | {{< icon name="check-circle" >}} Yes | GitLab [18.0](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | +| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926) | A user updates external status check response for merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | ### Security testing configuration diff --git a/ee/app/services/external_status_checks/responses/create_service.rb b/ee/app/services/external_status_checks/responses/create_service.rb index 91c5df6e9025ba..8f81d12ebc5765 100644 --- a/ee/app/services/external_status_checks/responses/create_service.rb +++ b/ee/app/services/external_status_checks/responses/create_service.rb @@ -48,7 +48,7 @@ def log_audit_event name: 'status_check_response_update', author: current_user, scope: project, - target: project, + target: merge_request, message: "Updated response for status check #{external_status_check.name} to #{status}", additional_details: { external_status_check_id: external_status_check.id, diff --git a/ee/config/audit_events/types/status_check_response_update.yml b/ee/config/audit_events/types/status_check_response_update.yml index acf0d771abbb8f..5519e80c4bb0ca 100644 --- a/ee/config/audit_events/types/status_check_response_update.yml +++ b/ee/config/audit_events/types/status_check_response_update.yml @@ -4,7 +4,7 @@ description: A user updates external status check response for merge request introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/413535 introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926 feature_category: security_policy_management -milestone: '18.0' -saved_to_database: true +milestone: '18.1' +saved_to_database: false streamed: true scope: [Project] diff --git a/ee/spec/services/external_status_checks/responses/create_service_spec.rb b/ee/spec/services/external_status_checks/responses/create_service_spec.rb index b25031ee92b995..ef31cc50c05da8 100644 --- a/ee/spec/services/external_status_checks/responses/create_service_spec.rb +++ b/ee/spec/services/external_status_checks/responses/create_service_spec.rb @@ -69,7 +69,7 @@ name: 'status_check_response_update', author: user, scope: project, - target: project, + target: merge_request, message: "Updated response for status check #{external_status_check.name} to passed", additional_details: { external_status_check_id: external_status_check.id, -- GitLab From fc3a9cc53c2b5a207d13b9d7036d049dd275e8cc Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Thu, 12 Jun 2025 12:32:10 +0200 Subject: [PATCH 04/12] Address MR comments --- .../responses/create_service.rb | 6 +- ee/spec/requests/api/status_checks_spec.rb | 197 ++++++++++++++++++ 2 files changed, 202 insertions(+), 1 deletion(-) diff --git a/ee/app/services/external_status_checks/responses/create_service.rb b/ee/app/services/external_status_checks/responses/create_service.rb index 8f81d12ebc5765..e0c30f7f8e88fd 100644 --- a/ee/app/services/external_status_checks/responses/create_service.rb +++ b/ee/app/services/external_status_checks/responses/create_service.rb @@ -19,7 +19,11 @@ def execute ServiceResponse.success(payload: { status_check_response: response }) else - ServiceResponse.error(message: response.errors.full_messages.to_sentence, reason: :bad_request) + ServiceResponse.error( + message: 'Failed to create status check response', + payload: { errors: response.errors.full_messages }, + reason: :bad_request + ) end end diff --git a/ee/spec/requests/api/status_checks_spec.rb b/ee/spec/requests/api/status_checks_spec.rb index d4162376964cc2..3c49c3fafb14b2 100644 --- a/ee/spec/requests/api/status_checks_spec.rb +++ b/ee/spec/requests/api/status_checks_spec.rb @@ -170,6 +170,203 @@ expect(response).to have_gitlab_http_status(:unauthorized) end end + + context 'when service returns validation errors' do + context 'when sha is missing' do + subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: external_status_check.id, status: status } } + + it 'returns bad request status with validation error message' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq('sha is missing') + end + + it 'does not create a status check response' do + expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } + end + end + + context 'when status is invalid' do + let(:status) { 'invalid_status' } + + it 'returns bad request status with validation error message' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to include('status does not have a valid value') + end + + it 'does not create a status check response' do + expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } + end + end + + context 'when status is missing' do + subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: external_status_check.id, sha: sha } } + + it 'returns bad request status with validation error message' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to include('status is missing') + end + + it 'does not create a status check response' do + expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } + end + end + + context 'when service returns validation errors from CreateService' do + before do + # Stub the service to return validation errors to test the render_api_error! line + allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error( + message: 'Validation failed', + reason: :bad_request, + payload: { errors: "Sha can't be blank" } + ) + ) + end + end + + it 'returns bad request status with service validation error message' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("Sha can't be blank") + end + + it 'does not create a status check response' do + expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } + end + end + end + + context 'when service returns permission errors' do + before do + # Remove user permissions to trigger permission error at API level + project.project_members.where(user: user).delete_all + project.add_member(user, :guest) + end + + it 'returns forbidden status' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when service returns not_found error' do + before do + # Stub the service to return a not_found error to test render_api_error! line + allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error( + message: 'External status check not found', + reason: :not_found, + payload: { errors: 'External status check not found' } + ) + ) + end + end + + it 'returns not found status with error message' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('External status check not found') + end + end + + context 'when service returns unprocessable_entity error' do + before do + # Stub the service to return an unprocessable_entity error to test render_api_error! line + allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error( + message: 'Cannot process request', + reason: :unprocessable_entity, + payload: { errors: 'Cannot process request' } + ) + ) + end + end + + it 'returns unprocessable entity status with error message' do + subject + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('Cannot process request') + end + end + + context 'when service returns multiple validation errors' do + before do + # Stub the service to return multiple validation errors to test render_api_error! line + allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error( + message: 'Validation failed', + reason: :bad_request, + payload: { errors: ["Sha can't be blank", "Status is not included in the list"] } + ) + ) + end + end + + it 'returns bad request status with multiple error messages' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(["Sha can't be blank", "Status is not included in the list"]) + end + end + + context 'when service returns forbidden error' do + before do + # Stub the service to return a forbidden error to test render_api_error! line + allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error( + message: 'Access denied', + reason: :forbidden, + payload: { errors: 'Access denied' } + ) + ) + end + end + + it 'returns forbidden status with error message' do + subject + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('Access denied') + end + end + + context 'when service returns internal_server_error' do + before do + # Stub the service to return an internal server error to test render_api_error! line + allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error( + message: 'Internal error occurred', + reason: :internal_server_error, + payload: { errors: 'Internal error occurred' } + ) + ) + end + end + + it 'returns internal server error status with error message' do + subject + + expect(response).to have_gitlab_http_status(:internal_server_error) + expect(json_response['message']).to eq('Internal error occurred') + end + end end end -- GitLab From 4df16c4c6fcda165cfe592b0ca88d018cec1081c Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Thu, 12 Jun 2025 12:36:06 +0200 Subject: [PATCH 05/12] Update compiled audit event docs --- doc/user/compliance/audit_event_types.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index b8d43cd4cdc872..df25e7f7e59477 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -543,7 +543,6 @@ Audit event types belong to the following product categories. | [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project | | [`policy_violations_resolved`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violations are resolved in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549812) | Project | | [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926) | A user updates external status check response for merge request | {{< icon name="check-circle" >}} Yes | GitLab [18.0](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | -| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926) | A user updates external status check response for merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | ### Security testing configuration -- GitLab From df2968ac2094844fc1514fd6405a9da2f7762f60 Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Thu, 12 Jun 2025 18:22:37 +0200 Subject: [PATCH 06/12] Fix failing specs --- ee/spec/requests/api/status_checks_spec.rb | 2 +- .../external_status_checks/responses/create_service_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/spec/requests/api/status_checks_spec.rb b/ee/spec/requests/api/status_checks_spec.rb index 3c49c3fafb14b2..7ca114af632a43 100644 --- a/ee/spec/requests/api/status_checks_spec.rb +++ b/ee/spec/requests/api/status_checks_spec.rb @@ -320,7 +320,7 @@ subject expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq(["Sha can't be blank", "Status is not included in the list"]) + expect(json_response['message']).to match_array(["Sha can't be blank", "Status is not included in the list"]) end end diff --git a/ee/spec/services/external_status_checks/responses/create_service_spec.rb b/ee/spec/services/external_status_checks/responses/create_service_spec.rb index ef31cc50c05da8..39be61ed086c30 100644 --- a/ee/spec/services/external_status_checks/responses/create_service_spec.rb +++ b/ee/spec/services/external_status_checks/responses/create_service_spec.rb @@ -95,7 +95,8 @@ result = service_response expect(result).to be_error - expect(result.message).to eq("Sha can't be blank") + expect(result.message).to eq('Failed to create status check response') + expect(result.payload[:errors]).to match_array(["Sha can't be blank"]) expect(result.reason).to eq(:bad_request) end end -- GitLab From aba0ad167dc5bb52debe468ac306ff298795f548 Mon Sep 17 00:00:00 2001 From: "Alan (Maciej) Paruszewski" Date: Fri, 13 Jun 2025 22:23:20 +0200 Subject: [PATCH 07/12] Apply 3 suggestion(s) to 3 file(s) Co-authored-by: Marcos Rocha --- .../services/external_status_checks/responses/create_service.rb | 2 +- ee/config/audit_events/types/status_check_response_update.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/app/services/external_status_checks/responses/create_service.rb b/ee/app/services/external_status_checks/responses/create_service.rb index e0c30f7f8e88fd..974696e47be79e 100644 --- a/ee/app/services/external_status_checks/responses/create_service.rb +++ b/ee/app/services/external_status_checks/responses/create_service.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module ExternalStatusChecks # rubocop:disable Gitlab/BoundedContexts -- TODO +module ExternalStatusChecks # rubocop:disable Gitlab/BoundedContexts -- -- This is an existing module module Responses class CreateService < BaseContainerService def execute diff --git a/ee/config/audit_events/types/status_check_response_update.yml b/ee/config/audit_events/types/status_check_response_update.yml index 5519e80c4bb0ca..241af412a3559b 100644 --- a/ee/config/audit_events/types/status_check_response_update.yml +++ b/ee/config/audit_events/types/status_check_response_update.yml @@ -2,7 +2,7 @@ name: status_check_response_update description: A user updates external status check response for merge request introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/413535 -introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189293 feature_category: security_policy_management milestone: '18.1' saved_to_database: false -- GitLab From c3517f04b1416b49772125075ea263bdc979463f Mon Sep 17 00:00:00 2001 From: Imam Hossain Date: Fri, 4 Jul 2025 12:56:47 +0200 Subject: [PATCH 08/12] Resolve MR reviews --- doc/user/compliance/audit_event_types.md | 2 +- ee/spec/requests/api/status_checks_spec.rb | 207 +++------------------ 2 files changed, 32 insertions(+), 177 deletions(-) diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index df25e7f7e59477..f9ee9b6809b742 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -538,11 +538,11 @@ Audit event types belong to the following product categories. | [`security_policy_create`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is created | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project | | [`security_policy_delete`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is deleted | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project | | [`security_policy_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is updated | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project | +| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189293) | A user updates external status check response for merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | | [`merge_request_branch_bypassed_by_security_policy`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195942) | The merge request's approval is bypassed by the branches configured in the security policy | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549646) | Project | | [`merge_request_merged_with_policy_violations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195775) | A merge request merged with security policy violations | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549813) | Project | | [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project | | [`policy_violations_resolved`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violations are resolved in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549812) | Project | -| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113926) | A user updates external status check response for merge request | {{< icon name="check-circle" >}} Yes | GitLab [18.0](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | ### Security testing configuration diff --git a/ee/spec/requests/api/status_checks_spec.rb b/ee/spec/requests/api/status_checks_spec.rb index 7ca114af632a43..94a7c995522595 100644 --- a/ee/spec/requests/api/status_checks_spec.rb +++ b/ee/spec/requests/api/status_checks_spec.rb @@ -100,6 +100,24 @@ let(:status) { 'passed' } + shared_examples 'returning the correct status' do |status| + it 'returns the correct status' do + expected_change = status == :created ? 1 : 0 + expect { subject }.to change { MergeRequests::StatusCheckResponse.count }.by(expected_change) + + expect(response).to have_gitlab_http_status(status) + end + end + + shared_examples 'returning the correct status and error' do |status, error| + it 'returns the correct status and error' do + expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } + + expect(response).to have_gitlab_http_status(status) + expect(json_response).to include(error.as_json) + end + end + context 'permissions' do using RSpec::Parameterized::TableSyntax @@ -137,89 +155,49 @@ project.add_member(user, :maintainer) if user end + context 'when the request is valid' do + it_behaves_like 'returning the correct status', :created + end + context 'when external status check ID does not belong to the requested project' do let_it_be(:external_status_check) { create(:external_status_check) } - it 'returns a not found status' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end + it_behaves_like 'returning the correct status', :not_found end context 'when sha is not the source branch HEAD' do let(:sha) { 'notarealsha' } - it 'does not create a new approval' do - expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } - end - - it 'returns a conflict error' do - subject - - expect(response).to have_gitlab_http_status(:conflict) - end + it_behaves_like 'returning the correct status', :conflict end context 'when user is not authenticated' do let(:user) { nil } - it 'returns an unauthorized status' do - subject - - expect(response).to have_gitlab_http_status(:unauthorized) - end + it_behaves_like 'returning the correct status', :unauthorized end context 'when service returns validation errors' do context 'when sha is missing' do subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: external_status_check.id, status: status } } - it 'returns bad request status with validation error message' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to eq('sha is missing') - end - - it 'does not create a status check response' do - expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } - end + it_behaves_like 'returning the correct status and error', :bad_request, error: 'sha is missing' end context 'when status is invalid' do let(:status) { 'invalid_status' } - it 'returns bad request status with validation error message' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to include('status does not have a valid value') - end - - it 'does not create a status check response' do - expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } - end + it_behaves_like 'returning the correct status and error', :bad_request, error: 'status does not have a valid value' end context 'when status is missing' do subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: external_status_check.id, sha: sha } } - it 'returns bad request status with validation error message' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to include('status is missing') - end - - it 'does not create a status check response' do - expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } - end + it_behaves_like 'returning the correct status and error', :bad_request, error: 'status is missing, status does not have a valid value' end - context 'when service returns validation errors from CreateService' do + context 'when service returns other validation errors' do before do - # Stub the service to return validation errors to test the render_api_error! line allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| allow(service).to receive(:execute).and_return( ServiceResponse.error( @@ -231,16 +209,7 @@ end end - it 'returns bad request status with service validation error message' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq("Sha can't be blank") - end - - it 'does not create a status check response' do - expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } - end + it_behaves_like 'returning the correct status and error', :bad_request, message: 'Sha can\'t be blank' end end @@ -251,121 +220,7 @@ project.add_member(user, :guest) end - it 'returns forbidden status' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - - context 'when service returns not_found error' do - before do - # Stub the service to return a not_found error to test render_api_error! line - allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| - allow(service).to receive(:execute).and_return( - ServiceResponse.error( - message: 'External status check not found', - reason: :not_found, - payload: { errors: 'External status check not found' } - ) - ) - end - end - - it 'returns not found status with error message' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['message']).to eq('External status check not found') - end - end - - context 'when service returns unprocessable_entity error' do - before do - # Stub the service to return an unprocessable_entity error to test render_api_error! line - allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| - allow(service).to receive(:execute).and_return( - ServiceResponse.error( - message: 'Cannot process request', - reason: :unprocessable_entity, - payload: { errors: 'Cannot process request' } - ) - ) - end - end - - it 'returns unprocessable entity status with error message' do - subject - - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response['message']).to eq('Cannot process request') - end - end - - context 'when service returns multiple validation errors' do - before do - # Stub the service to return multiple validation errors to test render_api_error! line - allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| - allow(service).to receive(:execute).and_return( - ServiceResponse.error( - message: 'Validation failed', - reason: :bad_request, - payload: { errors: ["Sha can't be blank", "Status is not included in the list"] } - ) - ) - end - end - - it 'returns bad request status with multiple error messages' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to match_array(["Sha can't be blank", "Status is not included in the list"]) - end - end - - context 'when service returns forbidden error' do - before do - # Stub the service to return a forbidden error to test render_api_error! line - allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| - allow(service).to receive(:execute).and_return( - ServiceResponse.error( - message: 'Access denied', - reason: :forbidden, - payload: { errors: 'Access denied' } - ) - ) - end - end - - it 'returns forbidden status with error message' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('Access denied') - end - end - - context 'when service returns internal_server_error' do - before do - # Stub the service to return an internal server error to test render_api_error! line - allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| - allow(service).to receive(:execute).and_return( - ServiceResponse.error( - message: 'Internal error occurred', - reason: :internal_server_error, - payload: { errors: 'Internal error occurred' } - ) - ) - end - end - - it 'returns internal server error status with error message' do - subject - - expect(response).to have_gitlab_http_status(:internal_server_error) - expect(json_response['message']).to eq('Internal error occurred') - end + it_behaves_like 'returning the correct status', :forbidden end end end -- GitLab From c4f8286daf45560f02876fc41d0c30b8fa4c8bdf Mon Sep 17 00:00:00 2001 From: Imam Hossain Date: Fri, 4 Jul 2025 13:00:08 +0200 Subject: [PATCH 09/12] Update audit event milestone --- doc/user/compliance/audit_event_types.md | 2 +- ee/config/audit_events/types/status_check_response_update.yml | 2 +- ee/spec/requests/api/status_checks_spec.rb | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index f9ee9b6809b742..1bce46ba56da28 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -538,7 +538,7 @@ Audit event types belong to the following product categories. | [`security_policy_create`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is created | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project | | [`security_policy_delete`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is deleted | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project | | [`security_policy_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192797) | A security policy is updated | {{< icon name="check-circle" >}} Yes | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/539230) | Project | -| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189293) | A user updates external status check response for merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.1](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | +| [`status_check_response_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189293) | A user updates external status check response for merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/413535) | Project | | [`merge_request_branch_bypassed_by_security_policy`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195942) | The merge request's approval is bypassed by the branches configured in the security policy | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549646) | Project | | [`merge_request_merged_with_policy_violations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195775) | A merge request merged with security policy violations | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549813) | Project | | [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project | diff --git a/ee/config/audit_events/types/status_check_response_update.yml b/ee/config/audit_events/types/status_check_response_update.yml index 241af412a3559b..1a6d3e5a894333 100644 --- a/ee/config/audit_events/types/status_check_response_update.yml +++ b/ee/config/audit_events/types/status_check_response_update.yml @@ -4,7 +4,7 @@ description: A user updates external status check response for merge request introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/413535 introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189293 feature_category: security_policy_management -milestone: '18.1' +milestone: '18.2' saved_to_database: false streamed: true scope: [Project] diff --git a/ee/spec/requests/api/status_checks_spec.rb b/ee/spec/requests/api/status_checks_spec.rb index 94a7c995522595..f426db62e6d1e0 100644 --- a/ee/spec/requests/api/status_checks_spec.rb +++ b/ee/spec/requests/api/status_checks_spec.rb @@ -215,7 +215,6 @@ context 'when service returns permission errors' do before do - # Remove user permissions to trigger permission error at API level project.project_members.where(user: user).delete_all project.add_member(user, :guest) end -- GitLab From b5ef2b8a5df1ec77273ae1fec18815ac81bdd6c8 Mon Sep 17 00:00:00 2001 From: Imam Hossain Date: Fri, 4 Jul 2025 13:21:21 +0200 Subject: [PATCH 10/12] Update rspec example --- ee/spec/requests/api/status_checks_spec.rb | 47 +++++++++++----------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/ee/spec/requests/api/status_checks_spec.rb b/ee/spec/requests/api/status_checks_spec.rb index f426db62e6d1e0..78237ebaaab111 100644 --- a/ee/spec/requests/api/status_checks_spec.rb +++ b/ee/spec/requests/api/status_checks_spec.rb @@ -100,21 +100,12 @@ let(:status) { 'passed' } - shared_examples 'returning the correct status' do |status| - it 'returns the correct status' do - expected_change = status == :created ? 1 : 0 - expect { subject }.to change { MergeRequests::StatusCheckResponse.count }.by(expected_change) - - expect(response).to have_gitlab_http_status(status) - end - end - - shared_examples 'returning the correct status and error' do |status, error| - it 'returns the correct status and error' do + shared_examples 'not creating a status check response and returns error' do |status, error = {}| + it 'does not create status check response and returns error' do expect { subject }.not_to change { MergeRequests::StatusCheckResponse.count } expect(response).to have_gitlab_http_status(status) - expect(json_response).to include(error.as_json) + expect(json_response).to include(error.as_json) if error.present? end end @@ -156,44 +147,54 @@ end context 'when the request is valid' do - it_behaves_like 'returning the correct status', :created + it 'creates a status check response and returns the correct status' do + expect { subject }.to change { MergeRequests::StatusCheckResponse.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + end end context 'when external status check ID does not belong to the requested project' do let_it_be(:external_status_check) { create(:external_status_check) } - it_behaves_like 'returning the correct status', :not_found + it_behaves_like 'not creating a status check response and returns error', :not_found, message: "404 Not found" end context 'when sha is not the source branch HEAD' do let(:sha) { 'notarealsha' } - it_behaves_like 'returning the correct status', :conflict + it_behaves_like 'not creating a status check response and returns error', :conflict end context 'when user is not authenticated' do let(:user) { nil } - it_behaves_like 'returning the correct status', :unauthorized + it_behaves_like 'not creating a status check response and returns error', :unauthorized, message: '401 Unauthorized' end context 'when service returns validation errors' do context 'when sha is missing' do - subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: external_status_check.id, status: status } } + subject do + post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), + params: { external_status_check_id: external_status_check.id, status: status } + end - it_behaves_like 'returning the correct status and error', :bad_request, error: 'sha is missing' + it_behaves_like 'not creating a status check response and returns error', :bad_request, error: 'sha is missing' end context 'when status is invalid' do let(:status) { 'invalid_status' } - it_behaves_like 'returning the correct status and error', :bad_request, error: 'status does not have a valid value' + it_behaves_like 'not creating a status check response and returns error', :bad_request, error: 'status does not have a valid value' end context 'when status is missing' do - subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), params: { external_status_check_id: external_status_check.id, sha: sha } } + subject do + post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/status_check_responses", user), + params: { external_status_check_id: external_status_check.id, sha: sha } + end - it_behaves_like 'returning the correct status and error', :bad_request, error: 'status is missing, status does not have a valid value' + it_behaves_like 'not creating a status check response and returns error', :bad_request, error: 'status is missing, status does not have a valid value' end context 'when service returns other validation errors' do @@ -209,7 +210,7 @@ end end - it_behaves_like 'returning the correct status and error', :bad_request, message: 'Sha can\'t be blank' + it_behaves_like 'not creating a status check response and returns error', :bad_request, message: 'Sha can\'t be blank' end end @@ -219,7 +220,7 @@ project.add_member(user, :guest) end - it_behaves_like 'returning the correct status', :forbidden + it_behaves_like 'not creating a status check response and returns error', :forbidden, message: '403 Forbidden' end end end -- GitLab From 1d5172272a8c3ea5fd6095f5028c8ece156a333e Mon Sep 17 00:00:00 2001 From: Imam Hossain Date: Mon, 7 Jul 2025 13:01:49 +0200 Subject: [PATCH 11/12] Resolve rubocop bounded context violation --- .../status_check_responses}/create_service.rb | 19 +++++++------------ ee/lib/api/status_checks.rb | 9 ++++----- ee/spec/requests/api/status_checks_spec.rb | 4 ++-- .../create_service_spec.rb | 10 +++++----- 4 files changed, 18 insertions(+), 24 deletions(-) rename ee/app/services/{external_status_checks/responses => merge_requests/status_check_responses}/create_service.rb (81%) rename ee/spec/services/{external_status_checks/responses => merge_requests/status_check_responses}/create_service_spec.rb (92%) diff --git a/ee/app/services/external_status_checks/responses/create_service.rb b/ee/app/services/merge_requests/status_check_responses/create_service.rb similarity index 81% rename from ee/app/services/external_status_checks/responses/create_service.rb rename to ee/app/services/merge_requests/status_check_responses/create_service.rb index 974696e47be79e..e3a908880fec28 100644 --- a/ee/app/services/external_status_checks/responses/create_service.rb +++ b/ee/app/services/merge_requests/status_check_responses/create_service.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -module ExternalStatusChecks # rubocop:disable Gitlab/BoundedContexts -- -- This is an existing module - module Responses - class CreateService < BaseContainerService - def execute +module MergeRequests + module StatusCheckResponses + class CreateService < BaseProjectService + def execute(merge_request) unless current_user.can?(:provide_status_check_response, merge_request) return ServiceResponse.error(message: 'Not Found', reason: :not_found) end @@ -15,7 +15,7 @@ def execute ) if response.save - log_audit_event + log_audit_event(merge_request) ServiceResponse.success(payload: { status_check_response: response }) else @@ -29,8 +29,6 @@ def execute private - attr_reader :current_user, :project - def status params[:status] end @@ -39,15 +37,11 @@ def sha params[:sha] end - def merge_request - params[:merge_request] - end - def external_status_check params[:external_status_check] end - def log_audit_event + def log_audit_event(merge_request) ::Gitlab::Audit::Auditor.audit( name: 'status_check_response_update', author: current_user, @@ -58,6 +52,7 @@ def log_audit_event external_status_check_id: external_status_check.id, external_status_check_name: external_status_check.name, status: status, + sha: sha, merge_request_id: merge_request.id, merge_request_iid: merge_request.iid } diff --git a/ee/lib/api/status_checks.rb b/ee/lib/api/status_checks.rb index 66b807ac59127a..8172523d866866 100644 --- a/ee/lib/api/status_checks.rb +++ b/ee/lib/api/status_checks.rb @@ -144,19 +144,18 @@ def check_feature_enabled! not_found! unless status_check check_sha_param!(params, merge_request) - result = ::ExternalStatusChecks::Responses::CreateService.new( - container: merge_request.project, + result = ::MergeRequests::StatusCheckResponses::CreateService.new( + project: merge_request.project, current_user: current_user, params: { - merge_request: merge_request, external_status_check: status_check, sha: params[:sha], status: params[:status] } - ).execute + ).execute(merge_request) if result.success? - present result.payload[:status_check_response], with: Entities::MergeRequests::StatusCheckResponse + present result[:status_check_response], with: Entities::MergeRequests::StatusCheckResponse else render_api_error!(result.payload[:errors], result.reason) end diff --git a/ee/spec/requests/api/status_checks_spec.rb b/ee/spec/requests/api/status_checks_spec.rb index 78237ebaaab111..e3c7acc915ae94 100644 --- a/ee/spec/requests/api/status_checks_spec.rb +++ b/ee/spec/requests/api/status_checks_spec.rb @@ -199,8 +199,8 @@ context 'when service returns other validation errors' do before do - allow_next_instance_of(::ExternalStatusChecks::Responses::CreateService) do |service| - allow(service).to receive(:execute).and_return( + allow_next_instance_of(::MergeRequests::StatusCheckResponses::CreateService) do |service| + allow(service).to receive(:execute).with(merge_request).and_return( ServiceResponse.error( message: 'Validation failed', reason: :bad_request, diff --git a/ee/spec/services/external_status_checks/responses/create_service_spec.rb b/ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb similarity index 92% rename from ee/spec/services/external_status_checks/responses/create_service_spec.rb rename to ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb index 39be61ed086c30..ed06fd39f6f1aa 100644 --- a/ee/spec/services/external_status_checks/responses/create_service_spec.rb +++ b/ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb @@ -2,28 +2,27 @@ require 'spec_helper' -RSpec.describe ExternalStatusChecks::Responses::CreateService, feature_category: :security_policy_management do +RSpec.describe MergeRequests::StatusCheckResponses::CreateService, feature_category: :security_policy_management do let_it_be(:project) { create(:project, :repository) } let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let_it_be(:external_status_check) { create(:external_status_check, project: project) } let_it_be(:user) { create(:user) } - let_it_be(:sha) { merge_request.diff_head_sha } + let_it_be(:sha) { SecureRandom.hex(20) } let_it_be(:status) { 'passed' } let(:service) do described_class.new( - container: project, + project: project, current_user: user, params: { external_status_check: external_status_check, - merge_request: merge_request, status: status, sha: sha }) end describe '#execute' do - subject(:service_response) { service.execute } + subject(:service_response) { service.execute(merge_request) } context 'when external status checks feature is disabled' do before do @@ -75,6 +74,7 @@ external_status_check_id: external_status_check.id, external_status_check_name: external_status_check.name, status: 'passed', + sha: sha, merge_request_id: merge_request.id, merge_request_iid: merge_request.iid } -- GitLab From 30aa94b994e601ae6bdac3bcbae6a8cab9c02186 Mon Sep 17 00:00:00 2001 From: Imam Hossain Date: Mon, 7 Jul 2025 13:14:20 +0200 Subject: [PATCH 12/12] Fix: Render API errors and spec for status check responses --- ee/lib/api/status_checks.rb | 2 +- .../status_check_responses/create_service_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/lib/api/status_checks.rb b/ee/lib/api/status_checks.rb index 8172523d866866..268f9e9a64b0cf 100644 --- a/ee/lib/api/status_checks.rb +++ b/ee/lib/api/status_checks.rb @@ -157,7 +157,7 @@ def check_feature_enabled! if result.success? present result[:status_check_response], with: Entities::MergeRequests::StatusCheckResponse else - render_api_error!(result.payload[:errors], result.reason) + render_api_error!(result[:errors], result.reason) end end diff --git a/ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb b/ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb index ed06fd39f6f1aa..0f9d48686a09b3 100644 --- a/ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb +++ b/ee/spec/services/merge_requests/status_check_responses/create_service_spec.rb @@ -4,7 +4,7 @@ RSpec.describe MergeRequests::StatusCheckResponses::CreateService, feature_category: :security_policy_management do let_it_be(:project) { create(:project, :repository) } - let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let_it_be_with_reload(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let_it_be(:external_status_check) { create(:external_status_check, project: project) } let_it_be(:user) { create(:user) } let_it_be(:sha) { SecureRandom.hex(20) } -- GitLab