From f7b966463de1b30d7568c1cc7f85120e23eaa123 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Wed, 13 May 2020 10:33:46 +0700 Subject: [PATCH 01/10] Refactor test service to service object Add ability to test with different events --- .../projects/services_controller.rb | 7 +-- .../integrations/project_test_data.rb | 57 +++++++++++++++++++ .../integrations/test/base_service.rb | 28 +++++++++ .../integrations/test/project_service.rb | 34 +++++++++++ 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 app/services/concerns/integrations/project_test_data.rb create mode 100644 app/services/integrations/test/base_service.rb create mode 100644 app/services/integrations/test/project_service.rb diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 5ed9baf04f6507..df6b90bca42db5 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -57,11 +57,10 @@ def service_test_response return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } end - data = @service.test_data(project, current_user) - outcome = @service.test(data) + result = Integrations::Test::ProjectService.new(@service, current_user, 'push').execute - unless outcome[:success] - return { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } + unless result[:success] + return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true } end {} diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb new file mode 100644 index 00000000000000..95040da17f4ed4 --- /dev/null +++ b/app/services/concerns/integrations/project_test_data.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Integrations + module ProjectTestData + private + + def push_events_data + return { error: s_('TestHooks|Ensure the project has at least one commit.') } if project.empty_repo? + + Gitlab::DataBuilder::Push.build_sample(project, current_user) + end + + def note_events_data + note = project.notes.first + return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present? + + Gitlab::DataBuilder::Note.build(note, current_user) + end + + def issues_events_data + issue = project.issues.first + return { error: s_('TestHooks|Ensure the project has issues.') } unless issue.present? + + issue.to_hook_data(current_user) + end + + def merge_requests_events_data + merge_request = project.merge_requests.first + return { error: s_('TestHooks|Ensure the project has merge requests.') } unless merge_request.present? + + merge_request.to_hook_data(current_user) + end + + def job_events_data + build = project.builds.first + return { error: s_('TestHooks|Ensure the project has CI jobs.') } unless build.present? + + Gitlab::DataBuilder::Build.build(build) + end + + def pipeline_events_data + pipeline = project.ci_pipelines.first + return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present? + + Gitlab::DataBuilder::Pipeline.build(pipeline) + end + + def wiki_page_events_data + page = project.wiki.list_pages(limit: 1).first + if !project.wiki_enabled? || page.blank? + return { error: s_('TestHooks|Ensure the wiki is enabled and has pages.') } + end + + Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create') + end + end +end diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb new file mode 100644 index 00000000000000..c65ebcadacf1a0 --- /dev/null +++ b/app/services/integrations/test/base_service.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Integrations + module Test + class BaseService < ::BaseService + attr_accessor :integration, :current_user, :event + + def initialize(integration, current_user, event) + @integration = integration + @current_user = current_user + @event = event + end + + def execute + return error('Testing not available for this event') if integration.supported_events.exclude?(event) || data.blank? + return error(data[:error]) if data[:error].present? + + integration.test(data) + end + + private + + def data + raise NotImplementedError + end + end + end +end diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb new file mode 100644 index 00000000000000..f3177132438f4d --- /dev/null +++ b/app/services/integrations/test/project_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Integrations + module Test + class ProjectService < Integrations::Test::BaseService + include Integrations::ProjectTestData + + def project + @project = integration.project + end + + private + + def data + case event + when 'push', 'tag_push' + push_events_data + when 'note' + note_events_data + when 'issues', 'confidential_issues' + issues_events_data + when 'merge_requests' + merge_requests_events_data + when 'job' + job_events_data + when 'pipeline' + pipeline_events_data + when 'wiki_page' + wiki_page_events_data + end + end + end + end +end -- GitLab From 594cedb35092d7179c0be97883b3998a00d12183 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Wed, 13 May 2020 10:48:42 +0700 Subject: [PATCH 02/10] Refactor test hooks to reuse code - Remove throw/catch statements in favor of returning an object. - Remove usage of send by replacing with a switch statement. - Memoize data value to avoid calling the method multiple times. --- .../integrations/test/project_service.rb | 33 +++++---- app/services/test_hooks/base_service.rb | 28 ++------ app/services/test_hooks/project_service.rb | 72 ++++++------------- app/services/test_hooks/system_service.rb | 25 ++++--- 4 files changed, 57 insertions(+), 101 deletions(-) diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index f3177132438f4d..c63660e63d4c46 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -4,6 +4,7 @@ module Integrations module Test class ProjectService < Integrations::Test::BaseService include Integrations::ProjectTestData + include Gitlab::Utils::StrongMemoize def project @project = integration.project @@ -12,21 +13,23 @@ def project private def data - case event - when 'push', 'tag_push' - push_events_data - when 'note' - note_events_data - when 'issues', 'confidential_issues' - issues_events_data - when 'merge_requests' - merge_requests_events_data - when 'job' - job_events_data - when 'pipeline' - pipeline_events_data - when 'wiki_page' - wiki_page_events_data + strong_memoize(:data) do + case event + when 'push_events', 'tag_push_events' + push_events_data + when 'note_events' + note_events_data + when 'issues_events', 'confidential_issues_events' + issues_events_data + when 'merge_requests_events' + merge_requests_events_data + when 'job_events' + job_events_data + when 'pipeline_events' + pipeline_events_data + when 'wiki_page_events' + wiki_page_events_data + end end end end diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb index ebebf29c28b450..aa96e1355ffc15 100644 --- a/app/services/test_hooks/base_service.rb +++ b/app/services/test_hooks/base_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module TestHooks - class BaseService + class BaseService < ::BaseService attr_accessor :hook, :current_user, :trigger def initialize(hook, current_user, trigger) @@ -12,31 +12,11 @@ def initialize(hook, current_user, trigger) def execute trigger_key = hook.class.triggers.key(trigger.to_sym) - trigger_data_method = "#{trigger}_data" - if trigger_key.nil? || !self.respond_to?(trigger_data_method, true) - return error('Testing not available for this hook') - end + return error('Testing not available for this hook') if trigger_key.nil? || data.blank? + return error(data[:error]) if data[:error].present? - error_message = catch(:validation_error) do # rubocop:disable Cop/BanCatchThrow - sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend - - return hook.execute(sample_data, trigger_key) # rubocop:disable Cop/AvoidReturnFromBlocks - end - - error(error_message) - end - - private - - def error(message, http_status = nil) - result = { - message: message, - status: :error - } - - result[:http_status] = http_status if http_status - result + hook.execute(data, trigger_key) end end end diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb index aa80cc928b94a0..4e554dce357af5 100644 --- a/app/services/test_hooks/project_service.rb +++ b/app/services/test_hooks/project_service.rb @@ -2,6 +2,9 @@ module TestHooks class ProjectService < TestHooks::BaseService + include Integrations::ProjectTestData + include Gitlab::Utils::StrongMemoize + attr_writer :project def project @@ -10,58 +13,25 @@ def project private - def push_events_data - throw(:validation_error, s_('TestHooks|Ensure the project has at least one commit.')) if project.empty_repo? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Push.build_sample(project, current_user) - end - - alias_method :tag_push_events_data, :push_events_data - - def note_events_data - note = project.notes.first - throw(:validation_error, s_('TestHooks|Ensure the project has notes.')) unless note.present? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Note.build(note, current_user) - end - - def issues_events_data - issue = project.issues.first - throw(:validation_error, s_('TestHooks|Ensure the project has issues.')) unless issue.present? # rubocop:disable Cop/BanCatchThrow - - issue.to_hook_data(current_user) - end - - alias_method :confidential_issues_events_data, :issues_events_data - - def merge_requests_events_data - merge_request = project.merge_requests.first - throw(:validation_error, s_('TestHooks|Ensure the project has merge requests.')) unless merge_request.present? # rubocop:disable Cop/BanCatchThrow - - merge_request.to_hook_data(current_user) - end - - def job_events_data - build = project.builds.first - throw(:validation_error, s_('TestHooks|Ensure the project has CI jobs.')) unless build.present? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Build.build(build) - end - - def pipeline_events_data - pipeline = project.ci_pipelines.first - throw(:validation_error, s_('TestHooks|Ensure the project has CI pipelines.')) unless pipeline.present? # rubocop:disable Cop/BanCatchThrow - - Gitlab::DataBuilder::Pipeline.build(pipeline) - end - - def wiki_page_events_data - page = project.wiki.list_pages(limit: 1).first - if !project.wiki_enabled? || page.blank? - throw(:validation_error, s_('TestHooks|Ensure the wiki is enabled and has pages.')) # rubocop:disable Cop/BanCatchThrow + def data + strong_memoize(:data) do + case trigger + when 'push_events', 'tag_push_events' + push_events_data + when 'note_events' + note_events_data + when 'issues_events', 'confidential_issues_events' + issues_events_data + when 'merge_requests_events' + merge_requests_events_data + when 'job_events' + job_events_data + when 'pipeline_events' + pipeline_events_data + when 'wiki_page_events' + wiki_page_events_data + end end - - Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create') end end end diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb index 5c7961f417de02..66d78bfc5787c4 100644 --- a/app/services/test_hooks/system_service.rb +++ b/app/services/test_hooks/system_service.rb @@ -2,23 +2,26 @@ module TestHooks class SystemService < TestHooks::BaseService - private - - def push_events_data - Gitlab::DataBuilder::Push.sample_data - end + include Gitlab::Utils::StrongMemoize - def tag_push_events_data - Gitlab::DataBuilder::Push.sample_data - end + private - def repository_update_events_data - Gitlab::DataBuilder::Repository.sample_data + def data + strong_memoize(:data) do + case trigger + when 'push_events', 'tag_push_events' + Gitlab::DataBuilder::Push.sample_data + when 'repository_update_events' + Gitlab::DataBuilder::Repository.sample_data + when 'merge_requests_events' + merge_requests_events_data + end + end end def merge_requests_events_data merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first - throw(:validation_error, s_('TestHooks|Ensure one of your projects has merge requests.')) unless merge_request.present? # rubocop:disable Cop/BanCatchThrow + return { error: s_('TestHooks|Ensure one of your projects has merge requests.') } unless merge_request.present? merge_request.to_hook_data(current_user) end -- GitLab From fee83937e3731aeb54782df694152e04992eb885 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Wed, 13 May 2020 23:19:46 +0700 Subject: [PATCH 03/10] Make event optional to fix non-event services - To preserve previous functionality, make sure that errors are only returned for services that have different events. This is needed because some of the services do not need event to be tested. - Allow event to be passed from params instead of hardcoding. --- app/controllers/projects/services_controller.rb | 2 +- app/services/integrations/test/base_service.rb | 8 +++++--- app/services/integrations/test/project_service.rb | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index df6b90bca42db5..aa99b7cb8d3b13 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -57,7 +57,7 @@ def service_test_response return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } end - result = Integrations::Test::ProjectService.new(@service, current_user, 'push').execute + result = Integrations::Test::ProjectService.new(@service, current_user, params[:event]).execute unless result[:success] return { error: true, message: _('Test failed.'), service_response: result[:message].to_s, test_failed: true } diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb index c65ebcadacf1a0..53b4772a2229eb 100644 --- a/app/services/integrations/test/base_service.rb +++ b/app/services/integrations/test/base_service.rb @@ -5,15 +5,17 @@ module Test class BaseService < ::BaseService attr_accessor :integration, :current_user, :event - def initialize(integration, current_user, event) + def initialize(integration, current_user, event = nil) @integration = integration @current_user = current_user @event = event end def execute - return error('Testing not available for this event') if integration.supported_events.exclude?(event) || data.blank? - return error(data[:error]) if data[:error].present? + if event + return error('Testing not available for this event') if integration.supported_events.exclude?(event) || data.blank? + return error(data[:error]) if data[:error].present? + end integration.test(data) end diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index c63660e63d4c46..1e6ead316aaec4 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -29,6 +29,8 @@ def data pipeline_events_data when 'wiki_page_events' wiki_page_events_data + else + push_events_data end end end -- GitLab From b9d31fad094a84fb95b3edde760499f132e57eae Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Thu, 14 May 2020 11:22:21 +0700 Subject: [PATCH 04/10] Remove error returned when project is an empty repo The build_sample method already includes a fallback in such a case which will send sample data instead of the actual project push. --- .../concerns/integrations/project_test_data.rb | 4 +--- locale/gitlab.pot | 3 --- spec/services/test_hooks/project_service_spec.rb | 14 -------------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb index 95040da17f4ed4..79380d15ed3941 100644 --- a/app/services/concerns/integrations/project_test_data.rb +++ b/app/services/concerns/integrations/project_test_data.rb @@ -5,8 +5,6 @@ module ProjectTestData private def push_events_data - return { error: s_('TestHooks|Ensure the project has at least one commit.') } if project.empty_repo? - Gitlab::DataBuilder::Push.build_sample(project, current_user) end @@ -39,7 +37,7 @@ def job_events_data end def pipeline_events_data - pipeline = project.ci_pipelines.first + pipeline = project.latest_pipeline_for_ref return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present? Gitlab::DataBuilder::Pipeline.build(pipeline) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d47a78c8c290ec..f776a2621036ba 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21656,9 +21656,6 @@ msgstr "" msgid "TestHooks|Ensure the project has CI pipelines." msgstr "" -msgid "TestHooks|Ensure the project has at least one commit." -msgstr "" - msgid "TestHooks|Ensure the project has issues." msgstr "" diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb index 8d30f5018dd36a..ab69a3b6e35f4c 100644 --- a/spec/services/test_hooks/project_service_spec.rb +++ b/spec/services/test_hooks/project_service_spec.rb @@ -31,13 +31,6 @@ let(:trigger) { 'push_events' } let(:trigger_key) { :push_hooks } - it 'returns error message if not enough data' do - allow(project).to receive(:empty_repo?).and_return(true) - - expect(hook).not_to receive(:execute) - expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' }) - end - it 'executes hook' do allow(project).to receive(:empty_repo?).and_return(false) allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) @@ -51,13 +44,6 @@ let(:trigger) { 'tag_push_events' } let(:trigger_key) { :tag_push_hooks } - it 'returns error message if not enough data' do - allow(project).to receive(:empty_repo?).and_return(true) - - expect(hook).not_to receive(:execute) - expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' }) - end - it 'executes hook' do allow(project).to receive(:empty_repo?).and_return(false) allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) -- GitLab From c075c872f59d52e1faa801080fed7dab5158d064 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Thu, 14 May 2020 14:38:18 +0700 Subject: [PATCH 05/10] Move rest of test_data logic to the service class Only 2 services have special cases that use pipeline data instead of push data, thus it's cleaner to move them to the service class. --- app/models/project_services/jira_service.rb | 5 --- .../pipelines_email_service.rb | 6 ---- app/models/service.rb | 4 --- .../integrations/project_test_data.rb | 2 +- .../integrations/test/project_service.rb | 34 +++++++++++-------- .../models/project_services/github_service.rb | 12 ------- .../services/user_activates_github_spec.rb | 1 - .../project_services/github_service_spec.rb | 17 ---------- .../pipelines_email_service_spec.rb | 16 --------- 9 files changed, 20 insertions(+), 77 deletions(-) diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index eee594ab4cab70..b1b2e60ba78157 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -211,11 +211,6 @@ def test(_) { success: success, result: result } end - # Jira does not need test data. - def test_data(_, _) - nil - end - override :support_close_issue? def support_close_issue? true diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index a58a264de5e598..c11b2f7cc6541c 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -56,12 +56,6 @@ def can_test? project&.ci_pipelines&.any? end - def test_data(project, user) - data = Gitlab::DataBuilder::Pipeline.build(project.ci_pipelines.last) - data[:user] = user.hook_attrs - data - end - def fields [ { type: 'textarea', diff --git a/app/models/service.rb b/app/models/service.rb index a2c23947932d75..42b0ae5c9db0f6 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -142,10 +142,6 @@ def to_data_fields_hash data_fields.as_json(only: data_fields.class.column_names).except('id', 'service_id') end - def test_data(project, user) - Gitlab::DataBuilder::Push.build_sample(project, user) - end - def event_channel_names [] end diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb index 79380d15ed3941..e6a3772a3dfeba 100644 --- a/app/services/concerns/integrations/project_test_data.rb +++ b/app/services/concerns/integrations/project_test_data.rb @@ -37,7 +37,7 @@ def job_events_data end def pipeline_events_data - pipeline = project.latest_pipeline_for_ref + pipeline = project.ci_pipelines.newest_first.first return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present? Gitlab::DataBuilder::Pipeline.build(pipeline) diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index 1e6ead316aaec4..010e88bc77a7db 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -14,23 +14,27 @@ def project def data strong_memoize(:data) do - case event - when 'push_events', 'tag_push_events' - push_events_data - when 'note_events' - note_events_data - when 'issues_events', 'confidential_issues_events' - issues_events_data - when 'merge_requests_events' - merge_requests_events_data - when 'job_events' - job_events_data - when 'pipeline_events' + if integration.is_a?(GithubService) || integration.is_a?(PipelinesEmailService) pipeline_events_data - when 'wiki_page_events' - wiki_page_events_data else - push_events_data + case event + when 'push_events', 'tag_push_events' + push_events_data + when 'note_events' + note_events_data + when 'issues_events', 'confidential_issues_events' + issues_events_data + when 'merge_requests_events' + merge_requests_events_data + when 'job_events' + job_events_data + when 'pipeline_events' + pipeline_events_data + when 'wiki_page_events' + wiki_page_events_data + else + push_events_data + end end end end diff --git a/ee/app/models/project_services/github_service.rb b/ee/app/models/project_services/github_service.rb index e17ba336ee7b76..c5ae970723c8d4 100644 --- a/ee/app/models/project_services/github_service.rb +++ b/ee/app/models/project_services/github_service.rb @@ -63,10 +63,6 @@ def can_test? project&.ci_pipelines&.any? end - def disabled_title - 'Please set up a pipeline on your repository.' - end - def execute(data) return if disabled? || invalid? || irrelevant_result?(data) @@ -75,14 +71,6 @@ def execute(data) update_status(status_message) end - def test_data(project, user) - pipeline = project.ci_pipelines.newest_first.first - - raise disabled_title unless pipeline - - Gitlab::DataBuilder::Pipeline.build(pipeline) - end - def test(data) begin result = execute(data) diff --git a/ee/spec/features/projects/services/user_activates_github_spec.rb b/ee/spec/features/projects/services/user_activates_github_spec.rb index 9532622ca763e7..edabec926213db 100644 --- a/ee/spec/features/projects/services/user_activates_github_spec.rb +++ b/ee/spec/features/projects/services/user_activates_github_spec.rb @@ -54,7 +54,6 @@ def fill_in_details ) click_button 'Test settings and save changes' - wait_for_requests expect(page).to have_content('GitHub activated.') end diff --git a/ee/spec/models/project_services/github_service_spec.rb b/ee/spec/models/project_services/github_service_spec.rb index acf4cb488530d9..0a659416c0b3c7 100644 --- a/ee/spec/models/project_services/github_service_spec.rb +++ b/ee/spec/models/project_services/github_service_spec.rb @@ -299,23 +299,6 @@ end end - describe '#test_data' do - let(:user) { project.owner } - let(:test_data) { subject.test_data(project, user) } - - it 'raises error if no pipeline found' do - project.ci_pipelines.delete_all - - expect { test_data }.to raise_error 'Please set up a pipeline on your repository.' - end - - it 'generates data for latest pipeline' do - pipeline - - expect(test_data[:object_kind]).to eq 'pipeline' - end - end - describe '#test' do it 'mentions creator in success message' do dummy_response = { context: "default", creator: { login: "YourUser" } } diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index f29414c80c9efd..de1edf2099a159 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -37,22 +37,6 @@ end end - describe '#test_data' do - let(:build) { create(:ci_build) } - let(:project) { build.project } - let(:user) { create(:user) } - - before do - project.add_developer(user) - end - - it 'builds test data' do - data = subject.test_data(project, user) - - expect(data[:object_kind]).to eq('pipeline') - end - end - shared_examples 'sending email' do |branches_to_be_notified: nil| before do subject.recipients = recipients -- GitLab From dabb97b5da886be5906ce518c20eb1d8e81eae00 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Thu, 14 May 2020 17:35:30 +0700 Subject: [PATCH 06/10] Make sure classes are scoped correctly --- app/services/integrations/test/project_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index 010e88bc77a7db..9862c730ee4f70 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -14,7 +14,7 @@ def project def data strong_memoize(:data) do - if integration.is_a?(GithubService) || integration.is_a?(PipelinesEmailService) + if integration.is_a?(::GithubService) || integration.is_a?(::PipelinesEmailService) pipeline_events_data else case event -- GitLab From 9b1a37445c762706cd3c08738a2d75e0b342f605 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Fri, 22 May 2020 00:12:29 +0700 Subject: [PATCH 07/10] Add specs and update event names - The supported_events of ChatNotificationService do not include the "_events" suffix but just have the action itself. - Add deployment as an event used by Slack - Add specs mostly adapted from TestHooks --- .../integrations/project_test_data.rb | 7 + .../integrations/test/project_service.rb | 20 +- ee/spec/factories/services.rb | 7 - locale/gitlab.pot | 3 + spec/factories/services.rb | 14 ++ .../integrations/test/project_service_spec.rb | 213 ++++++++++++++++++ 6 files changed, 249 insertions(+), 15 deletions(-) create mode 100644 spec/services/integrations/test/project_service_spec.rb diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb index e6a3772a3dfeba..4d5514303153e2 100644 --- a/app/services/concerns/integrations/project_test_data.rb +++ b/app/services/concerns/integrations/project_test_data.rb @@ -51,5 +51,12 @@ def wiki_page_events_data Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create') end + + def deployment_events_data + deployment = project.deployments.first + return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present? + + Gitlab::DataBuilder::Deployment.build(deployment) + end end end diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index 9862c730ee4f70..4930e991770a9e 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -7,7 +7,9 @@ class ProjectService < Integrations::Test::BaseService include Gitlab::Utils::StrongMemoize def project - @project = integration.project + strong_memoize(:project) do + integration.project + end end private @@ -18,20 +20,22 @@ def data pipeline_events_data else case event - when 'push_events', 'tag_push_events' + when 'push', 'tag_push' push_events_data - when 'note_events' + when 'note', 'confidential_note' note_events_data - when 'issues_events', 'confidential_issues_events' + when 'issue', 'confidential_issue' issues_events_data - when 'merge_requests_events' + when 'merge_request' merge_requests_events_data - when 'job_events' + when 'job' job_events_data - when 'pipeline_events' + when 'pipeline' pipeline_events_data - when 'wiki_page_events' + when 'wiki_page' wiki_page_events_data + when 'deployment' + deployment_events_data else push_events_data end diff --git a/ee/spec/factories/services.rb b/ee/spec/factories/services.rb index 89144c17b3dde0..4552e561892db8 100644 --- a/ee/spec/factories/services.rb +++ b/ee/spec/factories/services.rb @@ -7,13 +7,6 @@ type { 'GitlabSlackApplicationService' } end - factory :github_service do - project - active { true } - token { 'github-token' } - type { 'GithubService' } - end - factory :slack_slash_commands_service do project active { true } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f776a2621036ba..912953d06d7fb1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -21656,6 +21656,9 @@ msgstr "" msgid "TestHooks|Ensure the project has CI pipelines." msgstr "" +msgid "TestHooks|Ensure the project has deployments." +msgstr "" + msgid "TestHooks|Ensure the project has issues." msgstr "" diff --git a/spec/factories/services.rb b/spec/factories/services.rb index b6696769da91fa..5364f62b2c5648 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -165,6 +165,20 @@ type { 'SlackService' } end + factory :github_service do + project + type { 'GithubService' } + active { true } + token { 'github-token' } + end + + factory :pipelines_email_service do + project + active { true } + type { 'PipelinesEmailService' } + recipients { 'test@example.com' } + end + # this is for testing storing values inside properties, which is deprecated and will be removed in # https://gitlab.com/gitlab-org/gitlab/issues/29404 trait :without_properties_callback do diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb new file mode 100644 index 00000000000000..cd205b09474df4 --- /dev/null +++ b/spec/services/integrations/test/project_service_spec.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Integrations::Test::ProjectService do + let(:user) { create(:user) } + + describe '#execute' do + let(:project) { create(:project) } + let(:integration) { create(:slack_service, project: project) } + let(:event) { nil } + let(:sample_data) { { data: 'sample' } } + let(:success_result) { { success: true, result: {} } } + + subject { described_class.new(integration, user, event).execute } + + context 'without event specified' do + it 'tests the integration with default data' do + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + + context 'GitHubService' do + let(:integration) { create(:github_service, project: project) } + + it 'tests the integration with pipeline data' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'PipelinesEmailService' do + let(:integration) { create(:pipelines_email_service, project: project) } + + it 'tests the integration with pipeline data' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + end + + context 'with event specified' do + context 'event not supported by integration' do + let(:integration) { create(:jira_service, project: project) } + let(:event) { 'push' } + + it 'returns error message' do + expect(subject).to include({ status: :error, message: 'Testing not available for this event' }) + end + end + + context 'push' do + let(:event) { 'push' } + + it 'executes integration' do + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'tag_push' do + let(:event) { 'tag_push' } + + it 'executes integration' do + allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'note' do + let(:event) { 'note' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has notes.' }) + end + + it 'executes integration' do + allow(project).to receive(:notes).and_return([Note.new]) + allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'issue' do + let(:event) { 'issue' } + let(:issue) { build(:issue) } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has issues.' }) + end + + it 'executes integration' do + allow(project).to receive(:issues).and_return([issue]) + allow(issue).to receive(:to_hook_data).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'confidential_issue' do + let(:event) { 'confidential_issue' } + let(:issue) { build(:issue) } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has issues.' }) + end + + it 'executes integration' do + allow(project).to receive(:issues).and_return([issue]) + allow(issue).to receive(:to_hook_data).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'merge_request' do + let(:event) { 'merge_request' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has merge requests.' }) + end + + it 'executes integration' do + create(:merge_request, source_project: project) + allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'deployment' do + let(:project) { create(:project, :test_repo) } + let(:event) { 'deployment' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has deployments.' }) + end + + it 'executes integration' do + create(:deployment, project: project) + allow(Gitlab::DataBuilder::Deployment).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'pipeline' do + let(:event) { 'pipeline' } + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the project has CI pipelines.' }) + end + + it 'executes integration' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + + context 'wiki_page' do + let(:project) { create(:project, :wiki_repo) } + let(:event) { 'wiki_page' } + + it 'returns error message if wiki disabled' do + allow(project).to receive(:wiki_enabled?).and_return(false) + + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' }) + end + + it 'returns error message if not enough data' do + expect(integration).not_to receive(:test) + expect(subject).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' }) + end + + it 'executes integration' do + create(:wiki_page, wiki: project.wiki) + allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end + end + end + end +end -- GitLab From 97d8a4f55377a071b1e855947cc9959aa954516a Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Mon, 25 May 2020 12:45:16 +0700 Subject: [PATCH 08/10] Remove stubs and refactor specs - Use double for User since it's not used. - Extract shared specs --- .../integrations/test/project_service_spec.rb | 20 +++++++++---------- .../test_hooks/project_service_spec.rb | 2 -- .../test_hooks/system_service_spec.rb | 2 -- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb index cd205b09474df4..026a7750b1fb51 100644 --- a/spec/services/integrations/test/project_service_spec.rb +++ b/spec/services/integrations/test/project_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Integrations::Test::ProjectService do - let(:user) { create(:user) } + let(:user) { double('user') } describe '#execute' do let(:project) { create(:project) } @@ -22,9 +22,7 @@ expect(subject).to eq(success_result) end - context 'GitHubService' do - let(:integration) { create(:github_service, project: project) } - + shared_examples 'tests for integration with pipeline data' do it 'tests the integration with pipeline data' do create(:ci_empty_pipeline, project: project) allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) @@ -34,16 +32,16 @@ end end + context 'GitHubService' do + let(:integration) { create(:github_service, project: project) } + + it_behaves_like 'tests for integration with pipeline data' + end + context 'PipelinesEmailService' do let(:integration) { create(:pipelines_email_service, project: project) } - it 'tests the integration with pipeline data' do - create(:ci_empty_pipeline, project: project) - allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) - - expect(integration).to receive(:test).with(sample_data).and_return(success_result) - expect(subject).to eq(success_result) - end + it_behaves_like 'tests for integration with pipeline data' end end diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb index ab69a3b6e35f4c..3c5bc0d85f2d51 100644 --- a/spec/services/test_hooks/project_service_spec.rb +++ b/spec/services/test_hooks/project_service_spec.rb @@ -32,7 +32,6 @@ let(:trigger_key) { :push_hooks } it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result) @@ -45,7 +44,6 @@ let(:trigger_key) { :tag_push_hooks } it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data) expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result) diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb index 799b57eb04ee4d..8a86b14a2a1287 100644 --- a/spec/services/test_hooks/system_service_spec.rb +++ b/spec/services/test_hooks/system_service_spec.rb @@ -29,7 +29,6 @@ let(:trigger_key) { :push_hooks } it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) expect(Gitlab::DataBuilder::Push).to receive(:sample_data).and_call_original expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Push::SAMPLE_DATA, trigger_key).and_return(success_result) @@ -55,7 +54,6 @@ let(:trigger_key) { :repository_update_hooks } it 'executes hook' do - allow(project).to receive(:empty_repo?).and_return(false) expect(Gitlab::DataBuilder::Repository).to receive(:sample_data).and_call_original expect(hook).to receive(:execute).with(Gitlab::DataBuilder::Repository::SAMPLE_DATA, trigger_key).and_return(success_result) -- GitLab From f65dba9a3194f2e876337a6b34c4ea4a303361d0 Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Fri, 5 Jun 2020 15:45:04 +0700 Subject: [PATCH 09/10] Refactor to not inherit from BaseService - BaseService is meant for project-level services while our service classes can be used on the instance / group level. - Add rdoc style comments to make params of integration test service clearer. - Refactor "data" method to use early return using "next" instead of wrapping the whole block in a conditional. - Move guard clause for data errors to work without event specified when pipeline_events_data is returned. --- .../integrations/test/base_service.rb | 14 +++++-- .../integrations/test/project_service.rb | 40 +++++++++---------- app/services/test_hooks/base_service.rb | 4 +- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb index 53b4772a2229eb..a8a027092d5184 100644 --- a/app/services/integrations/test/base_service.rb +++ b/app/services/integrations/test/base_service.rb @@ -2,9 +2,14 @@ module Integrations module Test - class BaseService < ::BaseService + class BaseService + include BaseServiceUtility + attr_accessor :integration, :current_user, :event + # @param integration [Service] The external service that will be called + # @param current_user [User] The user calling the service + # @param event [String/nil] The event that triggered this def initialize(integration, current_user, event = nil) @integration = integration @current_user = current_user @@ -12,11 +17,12 @@ def initialize(integration, current_user, event = nil) end def execute - if event - return error('Testing not available for this event') if integration.supported_events.exclude?(event) || data.blank? - return error(data[:error]) if data[:error].present? + if event && (integration.supported_events.exclude?(event) || data.blank?) + return error('Testing not available for this event') end + return error(data[:error]) if data[:error].present? + integration.test(data) end diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index 4930e991770a9e..84e98a867882d9 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -16,29 +16,27 @@ def project def data strong_memoize(:data) do - if integration.is_a?(::GithubService) || integration.is_a?(::PipelinesEmailService) + next pipeline_events_data if integration.is_a?(::GithubService) || integration.is_a?(::PipelinesEmailService) + + case event + when 'push', 'tag_push' + push_events_data + when 'note', 'confidential_note' + note_events_data + when 'issue', 'confidential_issue' + issues_events_data + when 'merge_request' + merge_requests_events_data + when 'job' + job_events_data + when 'pipeline' pipeline_events_data + when 'wiki_page' + wiki_page_events_data + when 'deployment' + deployment_events_data else - case event - when 'push', 'tag_push' - push_events_data - when 'note', 'confidential_note' - note_events_data - when 'issue', 'confidential_issue' - issues_events_data - when 'merge_request' - merge_requests_events_data - when 'job' - job_events_data - when 'pipeline' - pipeline_events_data - when 'wiki_page' - wiki_page_events_data - when 'deployment' - deployment_events_data - else - push_events_data - end + push_events_data end end end diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb index aa96e1355ffc15..0fda6fb1ed0745 100644 --- a/app/services/test_hooks/base_service.rb +++ b/app/services/test_hooks/base_service.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true module TestHooks - class BaseService < ::BaseService + class BaseService + include BaseServiceUtility + attr_accessor :hook, :current_user, :trigger def initialize(hook, current_user, trigger) -- GitLab From 0abf1bc25eb1e7a62137d299374ed2927c01287e Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Fri, 5 Jun 2020 17:37:05 +0700 Subject: [PATCH 10/10] Move GithubService to EE code GithubService is only available in EE code thus needs to be handled separately to not affect FOSS code. --- .../integrations/test/project_service.rb | 4 +++- .../ee/integrations/test/project_service.rb | 23 ++++++++++++++++++ .../integrations/test/project_service_spec.rb | 24 +++++++++++++++++++ .../integrations/test/project_service_spec.rb | 16 ------------- .../integrations/test_examples.rb | 11 +++++++++ 5 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 ee/app/services/ee/integrations/test/project_service.rb create mode 100644 ee/spec/services/ee/integrations/test/project_service_spec.rb create mode 100644 spec/support/shared_examples/integrations/test_examples.rb diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb index 84e98a867882d9..941d70c2cc4487 100644 --- a/app/services/integrations/test/project_service.rb +++ b/app/services/integrations/test/project_service.rb @@ -16,7 +16,7 @@ def project def data strong_memoize(:data) do - next pipeline_events_data if integration.is_a?(::GithubService) || integration.is_a?(::PipelinesEmailService) + next pipeline_events_data if integration.is_a?(::PipelinesEmailService) case event when 'push', 'tag_push' @@ -43,3 +43,5 @@ def data end end end + +Integrations::Test::ProjectService.prepend_if_ee('::EE::Integrations::Test::ProjectService') diff --git a/ee/app/services/ee/integrations/test/project_service.rb b/ee/app/services/ee/integrations/test/project_service.rb new file mode 100644 index 00000000000000..e3c03de73823c9 --- /dev/null +++ b/ee/app/services/ee/integrations/test/project_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module EE + module Integrations + module Test + module ProjectService + extend ::Gitlab::Utils::Override + include ::Gitlab::Utils::StrongMemoize + + private + + override :data + def data + strong_memoize(:data) do + next pipeline_events_data if integration.is_a?(::GithubService) + + super + end + end + end + end + end +end diff --git a/ee/spec/services/ee/integrations/test/project_service_spec.rb b/ee/spec/services/ee/integrations/test/project_service_spec.rb new file mode 100644 index 00000000000000..270cf55122cde9 --- /dev/null +++ b/ee/spec/services/ee/integrations/test/project_service_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Integrations::Test::ProjectService do + let(:user) { double('user') } + + describe '#execute' do + let(:project) { create(:project) } + let(:event) { nil } + let(:sample_data) { { data: 'sample' } } + let(:success_result) { { success: true, result: {} } } + + subject { described_class.new(integration, user, event).execute } + + context 'without event specified' do + context 'GitHubService' do + let(:integration) { create(:github_service, project: project) } + + it_behaves_like 'tests for integration with pipeline data' + end + end + end +end diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb index 026a7750b1fb51..fdb43ca345a7ba 100644 --- a/spec/services/integrations/test/project_service_spec.rb +++ b/spec/services/integrations/test/project_service_spec.rb @@ -22,22 +22,6 @@ expect(subject).to eq(success_result) end - shared_examples 'tests for integration with pipeline data' do - it 'tests the integration with pipeline data' do - create(:ci_empty_pipeline, project: project) - allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) - - expect(integration).to receive(:test).with(sample_data).and_return(success_result) - expect(subject).to eq(success_result) - end - end - - context 'GitHubService' do - let(:integration) { create(:github_service, project: project) } - - it_behaves_like 'tests for integration with pipeline data' - end - context 'PipelinesEmailService' do let(:integration) { create(:pipelines_email_service, project: project) } diff --git a/spec/support/shared_examples/integrations/test_examples.rb b/spec/support/shared_examples/integrations/test_examples.rb new file mode 100644 index 00000000000000..eb2e83ce5d1d12 --- /dev/null +++ b/spec/support/shared_examples/integrations/test_examples.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'tests for integration with pipeline data' do + it 'tests the integration with pipeline data' do + create(:ci_empty_pipeline, project: project) + allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data) + + expect(integration).to receive(:test).with(sample_data).and_return(success_result) + expect(subject).to eq(success_result) + end +end -- GitLab