diff options
Diffstat (limited to 'spec/services')
34 files changed, 1101 insertions, 341 deletions
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index 33cd1f37ff6..adb5219d691 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -62,6 +62,54 @@ describe ApplicationSettings::UpdateService do end end + describe 'updating outbound_local_requests_whitelist' do + context 'when params is blank' do + let(:params) { {} } + + it 'does not add to whitelist' do + expect { subject.execute }.not_to change { + application_settings.outbound_local_requests_whitelist + } + end + end + + context 'when param add_to_outbound_local_requests_whitelist contains values' do + before do + application_settings.outbound_local_requests_whitelist = ['127.0.0.1'] + end + + let(:params) { { add_to_outbound_local_requests_whitelist: ['example.com', ''] } } + + it 'adds to whitelist' do + expect { subject.execute }.to change { + application_settings.outbound_local_requests_whitelist + } + + expect(application_settings.outbound_local_requests_whitelist).to contain_exactly( + '127.0.0.1', 'example.com' + ) + end + end + + context 'when param outbound_local_requests_whitelist_raw is passed' do + before do + application_settings.outbound_local_requests_whitelist = ['127.0.0.1'] + end + + let(:params) { { outbound_local_requests_whitelist_raw: 'example.com;gitlab.com' } } + + it 'overwrites the existing whitelist' do + expect { subject.execute }.to change { + application_settings.outbound_local_requests_whitelist + } + + expect(application_settings.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', 'gitlab.com' + ) + end + end + end + describe 'performance bar settings' do using RSpec::Parameterized::TableSyntax diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 4f4776bbb27..3ca389ba25b 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -145,6 +145,19 @@ describe Auth::ContainerRegistryAuthenticationService do it_behaves_like 'not a container repository factory' end + describe '#pull_access_token' do + let(:project) { create(:project) } + let(:token) { described_class.pull_access_token(project.full_path) } + + subject { { token: token } } + + it_behaves_like 'an accessible' do + let(:actions) { ['pull'] } + end + + it_behaves_like 'not a container repository factory' + end + context 'user authorization' do let(:current_user) { create(:user) } diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 40878e24cb4..931b67b2950 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -112,7 +112,7 @@ describe Boards::Issues::ListService do it_behaves_like 'issues list service' end - context 'and group is an ancestor', :nested_groups do + context 'and group is an ancestor' do let(:parent) { create(:group) } let(:group) { create(:group, parent: parent) } let!(:backlog) { create(:backlog_list, board: board) } diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index d9b61dfe503..7e2f311a065 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1099,6 +1099,62 @@ describe Ci::CreatePipelineService do end end end + + context 'when needs is used' do + let(:pipeline) { execute_service } + + let(:config) do + { + build_a: { + stage: "build", + script: "ls", + only: %w[master] + }, + test_a: { + stage: "test", + script: "ls", + only: %w[master feature tags], + needs: %w[build_a] + }, + deploy: { + stage: "deploy", + script: "ls", + only: %w[tags] + } + } + end + + before do + stub_ci_pipeline_yaml_file(YAML.dump(config)) + end + + context 'when pipeline on master is created' do + let(:ref_name) { 'refs/heads/master' } + + it 'creates a pipeline with build_a and test_a' do + expect(pipeline).to be_persisted + expect(pipeline.builds.map(&:name)).to contain_exactly("build_a", "test_a") + end + end + + context 'when pipeline on feature is created' do + let(:ref_name) { 'refs/heads/feature' } + + it 'does not create a pipeline as test_a depends on build_a' do + expect(pipeline).not_to be_persisted + expect(pipeline.builds).to be_empty + end + end + + context 'when pipeline on v1.0.0 is created' do + let(:ref_name) { 'refs/tags/v1.0.0' } + + it 'does create a pipeline only with deploy' do + expect(pipeline).to be_persisted + expect(pipeline.builds.map(&:name)).to contain_exactly("deploy") + end + end + end end describe '#execute!' do diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index 1e68b7956ea..cf39f3da4fe 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -60,6 +60,19 @@ describe Ci::PlayBuildService, '#execute' do expect(build.reload.user).to eq user end + + context 'when variables are supplied' do + let(:job_variables) do + [{ key: 'first', secret_value: 'first' }, + { key: 'second', secret_value: 'second' }] + end + + it 'assigns the variables to the build' do + service.execute(build, job_variables) + + expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second') + end + end end context 'when build is not a playable manual action' do diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index cadb519ccee..1b28d2d4d02 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -700,6 +700,138 @@ describe Ci::ProcessPipelineService, '#execute' do end end + context 'when pipeline with needs is created' do + let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) } + let!(:mac_build) { create_build('mac:build', stage: 'build', stage_idx: 0) } + let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) } + let!(:linux_rubocop) { create_build('linux:rubocop', stage: 'test', stage_idx: 1) } + let!(:mac_rspec) { create_build('mac:rspec', stage: 'test', stage_idx: 1) } + let!(:mac_rubocop) { create_build('mac:rubocop', stage: 'test', stage_idx: 1) } + let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 2) } + + let!(:linux_rspec_on_build) { create(:ci_build_need, build: linux_rspec, name: 'linux:build') } + let!(:linux_rubocop_on_build) { create(:ci_build_need, build: linux_rubocop, name: 'linux:build') } + + let!(:mac_rspec_on_build) { create(:ci_build_need, build: mac_rspec, name: 'mac:build') } + let!(:mac_rubocop_on_build) { create(:ci_build_need, build: mac_rubocop, name: 'mac:build') } + + it 'when linux:* finishes first it runs it out of order' do + expect(process_pipeline).to be_truthy + + expect(stages).to eq(%w(pending created created)) + expect(builds.pending).to contain_exactly(linux_build, mac_build) + + # we follow the single path of linux + linux_build.reset.success! + + expect(stages).to eq(%w(running pending created)) + expect(builds.success).to contain_exactly(linux_build) + expect(builds.pending).to contain_exactly(mac_build, linux_rspec, linux_rubocop) + + linux_rspec.reset.success! + + expect(stages).to eq(%w(running running created)) + expect(builds.success).to contain_exactly(linux_build, linux_rspec) + expect(builds.pending).to contain_exactly(mac_build, linux_rubocop) + + linux_rubocop.reset.success! + + expect(stages).to eq(%w(running running created)) + expect(builds.success).to contain_exactly(linux_build, linux_rspec, linux_rubocop) + expect(builds.pending).to contain_exactly(mac_build) + + mac_build.reset.success! + mac_rspec.reset.success! + mac_rubocop.reset.success! + + expect(stages).to eq(%w(success success pending)) + expect(builds.success).to contain_exactly( + linux_build, linux_rspec, linux_rubocop, mac_build, mac_rspec, mac_rubocop) + expect(builds.pending).to contain_exactly(deploy) + end + + context 'when feature ci_dag_support is disabled' do + before do + stub_feature_flags(ci_dag_support: false) + end + + it 'when linux:build finishes first it follows stages' do + expect(process_pipeline).to be_truthy + + expect(stages).to eq(%w(pending created created)) + expect(builds.pending).to contain_exactly(linux_build, mac_build) + + # we follow the single path of linux + linux_build.reset.success! + + expect(stages).to eq(%w(running created created)) + expect(builds.success).to contain_exactly(linux_build) + expect(builds.pending).to contain_exactly(mac_build) + + mac_build.reset.success! + + expect(stages).to eq(%w(success pending created)) + expect(builds.success).to contain_exactly(linux_build, mac_build) + expect(builds.pending).to contain_exactly( + linux_rspec, linux_rubocop, mac_rspec, mac_rubocop) + + linux_rspec.reset.success! + linux_rubocop.reset.success! + mac_rspec.reset.success! + mac_rubocop.reset.success! + + expect(stages).to eq(%w(success success pending)) + expect(builds.success).to contain_exactly( + linux_build, linux_rspec, linux_rubocop, mac_build, mac_rspec, mac_rubocop) + expect(builds.pending).to contain_exactly(deploy) + end + end + + context 'when one of the jobs is run on a failure' do + let!(:linux_notify) { create_build('linux:notify', stage: 'deploy', stage_idx: 2, when: 'on_failure') } + + let!(:linux_notify_on_build) { create(:ci_build_need, build: linux_notify, name: 'linux:build') } + + context 'when another job in build phase fails first' do + context 'when ci_dag_support is enabled' do + it 'does skip linux:notify' do + expect(process_pipeline).to be_truthy + + mac_build.reset.drop! + linux_build.reset.success! + + expect(linux_notify.reset).to be_skipped + end + end + + context 'when ci_dag_support is disabled' do + before do + stub_feature_flags(ci_dag_support: false) + end + + it 'does run linux:notify' do + expect(process_pipeline).to be_truthy + + mac_build.reset.drop! + linux_build.reset.success! + + expect(linux_notify.reset).to be_pending + end + end + end + + context 'when linux:build job fails first' do + it 'does run linux:notify' do + expect(process_pipeline).to be_truthy + + linux_build.reset.drop! + + expect(linux_notify.reset).to be_pending + end + end + end + end + def process_pipeline described_class.new(pipeline.project, user).execute(pipeline) end @@ -712,6 +844,10 @@ describe Ci::ProcessPipelineService, '#execute' do all_builds.where.not(status: [:created, :skipped]) end + def stages + pipeline.reset.stages.map(&:status) + end + def builds_names builds.pluck(:name) end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 11b06ef5019..fe7c6fe4700 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -30,7 +30,8 @@ describe Ci::RetryBuildService do job_artifacts_sast job_artifacts_dependency_scanning job_artifacts_container_scanning job_artifacts_dast job_artifacts_license_management job_artifacts_performance - job_artifacts_codequality job_artifacts_metrics scheduled_at].freeze + job_artifacts_codequality job_artifacts_metrics scheduled_at + job_variables].freeze IGNORE_ACCESSORS = %i[type lock_version target_url base_tags trace_sections @@ -65,6 +66,9 @@ describe Ci::RetryBuildService do file_type: file_type, job: build, expire_at: build.artifacts_expire_at) end + create(:ci_job_variable, job: build) + create(:ci_build_need, build: build) + build.reload end diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb deleted file mode 100644 index 5bc8a709941..00000000000 --- a/spec/services/clusters/refresh_service_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Clusters::RefreshService do - shared_examples 'creates a kubernetes namespace' do - let(:token) { 'aaaaaa' } - let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) } - let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) } - - it 'creates a kubernetes namespace' do - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator) - expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher) - - expect { subject }.to change(project.kubernetes_namespaces, :count) - - kubernetes_namespace = cluster.kubernetes_namespaces.first - expect(kubernetes_namespace).to be_present - expect(kubernetes_namespace.project).to eq(project) - end - end - - shared_examples 'does not create a kubernetes namespace' do - it 'does not create a new kubernetes namespace' do - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).not_to receive(:namespace_creator) - expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).not_to receive(:new) - - expect { subject }.not_to change(Clusters::KubernetesNamespace, :count) - end - end - - describe '.create_or_update_namespaces_for_cluster' do - let(:cluster) { create(:cluster, :provided_by_user, :project) } - let(:project) { cluster.project } - - subject { described_class.create_or_update_namespaces_for_cluster(cluster) } - - context 'cluster is project level' do - include_examples 'creates a kubernetes namespace' - - context 'when project already has kubernetes namespace' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - include_examples 'does not create a kubernetes namespace' - end - end - - context 'cluster is group level' do - let(:cluster) { create(:cluster, :provided_by_user, :group) } - let(:group) { cluster.group } - let(:project) { create(:project, group: group) } - - include_examples 'creates a kubernetes namespace' - - context 'when project already has kubernetes namespace' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - include_examples 'does not create a kubernetes namespace' - end - end - end - - describe '.create_or_update_namespaces_for_project' do - let(:project) { create(:project) } - - subject { described_class.create_or_update_namespaces_for_project(project) } - - it 'creates no kubernetes namespaces' do - expect { subject }.not_to change(project.kubernetes_namespaces, :count) - end - - context 'project has a project cluster' do - let!(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :project_type, projects: [project]) } - - include_examples 'creates a kubernetes namespace' - - context 'when project already has kubernetes namespace' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - include_examples 'does not create a kubernetes namespace' - end - end - - context 'project belongs to a group cluster' do - let!(:cluster) { create(:cluster, :provided_by_gcp, :group) } - - let(:group) { cluster.group } - let(:project) { create(:project, group: group) } - - include_examples 'does not create a kubernetes namespace' - - context 'when project already has kubernetes namespace' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - include_examples 'does not create a kubernetes namespace' - end - end - - context 'cluster is not managed' do - let!(:cluster) { create(:cluster, :project, :not_managed, projects: [project]) } - - include_examples 'does not create a kubernetes namespace' - end - end -end diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb index 7f8ab517cef..7591b2f6f12 100644 --- a/spec/services/groups/auto_devops_service_spec.rb +++ b/spec/services/groups/auto_devops_service_spec.rb @@ -47,7 +47,7 @@ describe Groups::AutoDevopsService, '#execute' do expect(subgroup_1.auto_devops_enabled?).to eq(false) end - context 'when subgroups have projects', :nested_groups do + context 'when subgroups have projects' do it 'reflects changes on projects' do subgroup_1 = create(:group, parent: group) project_1 = create(:project, namespace: subgroup_1) diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index a7c95428485..0f9f20de586 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -44,7 +44,7 @@ describe Groups::CreateService, '#execute' do end end - describe 'creating subgroup', :nested_groups do + describe 'creating subgroup' do let!(:group) { create(:group) } let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) } @@ -54,47 +54,31 @@ describe Groups::CreateService, '#execute' do end it { is_expected.to be_persisted } + end - context 'when nested groups feature is disabled' do - it 'does not save group and returns an error' do - allow(Group).to receive(:supports_nested_objects?).and_return(false) + context 'as guest' do + it 'does not save group and returns an error' do + is_expected.not_to be_persisted - is_expected.not_to be_persisted - expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.') - expect(subject.parent_id).to be_nil - end + expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.') + expect(subject.parent_id).to be_nil end end - context 'when nested groups feature is enabled' do + context 'as owner' do before do - allow(Group).to receive(:supports_nested_objects?).and_return(true) - end - - context 'as guest' do - it 'does not save group and returns an error' do - is_expected.not_to be_persisted - - expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.') - expect(subject.parent_id).to be_nil - end + group.add_owner(user) end - context 'as owner' do - before do - group.add_owner(user) - end + it { is_expected.to be_persisted } + end - it { is_expected.to be_persisted } + context 'as maintainer' do + before do + group.add_maintainer(user) end - context 'as maintainer' do - before do - group.add_maintainer(user) - end - - it { is_expected.to be_persisted } - end + it { is_expected.to be_persisted } end end diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb index 13acf9e055b..b30392c1b12 100644 --- a/spec/services/groups/nested_create_service_spec.rb +++ b/spec/services/groups/nested_create_service_spec.rb @@ -28,35 +28,7 @@ describe Groups::NestedCreateService do end end - describe 'without subgroups' do - let(:params) { { group_path: 'a-group' } } - - before do - allow(Group).to receive(:supports_nested_objects?) { false } - end - - it 'creates the group' do - group = service.execute - - expect(group).to be_persisted - end - - it 'returns the group if it already existed' do - existing_group = create(:group, path: 'a-group') - - expect(service.execute).to eq(existing_group) - end - - it 'raises an error when tring to create a subgroup' do - service = described_class.new(user, group_path: 'a-group/a-sub-group') - - expect { service.execute }.to raise_error('Nested groups are not supported on MySQL') - end - - it_behaves_like 'with a visibility level' - end - - describe 'with subgroups', :nested_groups do + describe 'with subgroups' do let(:params) { { group_path: 'a-group/a-sub-group' } } describe "#execute" do diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index b5708ebba76..f3af8cf5f3b 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -2,28 +2,13 @@ require 'rails_helper' -describe Groups::TransferService, :postgresql do +describe Groups::TransferService do let(:user) { create(:user) } let(:new_parent_group) { create(:group, :public) } let!(:group_member) { create(:group_member, :owner, group: group, user: user) } let(:transfer_service) { described_class.new(group, user) } shared_examples 'ensuring allowed transfer for a group' do - context 'with other database than PostgreSQL' do - before do - allow(Group).to receive(:supports_nested_objects?).and_return(false) - end - - it 'returns false' do - expect(transfer_service.execute(new_parent_group)).to be_falsy - end - - it 'adds an error on group' do - transfer_service.execute(new_parent_group) - expect(transfer_service.error).to eq('Transfer failed: Database is not supported.') - end - end - context "when there's an exception on GitLab shell directories" do let(:new_parent_group) { create(:group, :public) } diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index d081c20f669..5d4576139f7 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -133,7 +133,7 @@ describe Groups::UpdateService do end end - context 'for a subgroup', :nested_groups do + context 'for a subgroup' do let(:subgroup) { create(:group, :private, parent: private_group) } context 'when the parent group share_with_group_lock is enabled' do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 68b79132096..d9f35afee06 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -116,7 +116,7 @@ describe Issues::UpdateService, :mailer do expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) end - context 'when moving issue between issues from different projects', :nested_groups do + context 'when moving issue between issues from different projects' do let(:group) { create(:group) } let(:subgroup) { create(:group, parent: group) } @@ -226,6 +226,15 @@ describe Issues::UpdateService, :mailer do end end + it 'creates zoom_link_added system note when a zoom link is added to the description' do + update_issue(description: 'Changed description https://zoom.us/j/5873603787') + + note = find_note('a Zoom call was added') + + expect(note).not_to be_nil + expect(note.note).to eq('a Zoom call was added to this issue') + end + context 'when issue turns confidential' do let(:opts) do { @@ -703,7 +712,7 @@ describe Issues::UpdateService, :mailer do end end - context 'when moving an issue ', :nested_groups do + context 'when moving an issue ' do it 'raises an error for invalid move ids within a project' do opts = { move_between_ids: [9000, 9999] } diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 52f9a305d8f..7dce7f035d4 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -267,15 +267,15 @@ describe Members::DestroyService do expect(group.members.map(&:user)).not_to include(member_user) end - it 'removes the subgroup membership', :postgresql do + it 'removes the subgroup membership' do expect(subgroup.members.map(&:user)).not_to include(member_user) end - it 'removes the subsubgroup membership', :postgresql do + it 'removes the subsubgroup membership' do expect(subsubgroup.members.map(&:user)).not_to include(member_user) end - it 'removes the subsubproject membership', :postgresql do + it 'removes the subsubproject membership' do expect(subsubproject.members.map(&:user)).not_to include(member_user) end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 5c3b209086c..f18239f6d39 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - require 'spec_helper' describe MergeRequests::BuildService do @@ -225,6 +224,11 @@ describe MergeRequests::BuildService do let(:label_ids) { [label2.id] } let(:milestone_id) { milestone2.id } + before do + # Guests are not able to assign labels or milestones to an issue + project.add_developer(user) + end + it 'assigns milestone_id and label_ids instead of issue labels and milestone' do expect(merge_request.milestone).to eq(milestone2) expect(merge_request.labels).to match_array([label2]) @@ -479,4 +483,35 @@ describe MergeRequests::BuildService do end end end + + context 'when assigning labels' do + let(:label_ids) { [create(:label, project: project).id] } + + context 'for members with less than developer access' do + it 'is not allowed' do + expect(merge_request.label_ids).to be_empty + end + end + + context 'for users allowed to assign labels' do + before do + project.add_developer(user) + end + + context 'for labels in the project' do + it 'is allowed for developers' do + expect(merge_request.label_ids).to contain_exactly(*label_ids) + end + end + + context 'for unrelated labels' do + let(:project_label) { create(:label, project: project) } + let(:label_ids) { [create(:label).id, project_label.id] } + + it 'only assigns related labels' do + expect(merge_request.label_ids).to contain_exactly(project_label.id) + end + end + end + end end diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb index 6e827f2ea5b..a864da0a6fb 100644 --- a/spec/services/merge_requests/mergeability_check_service_spec.rb +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe MergeRequests::MergeabilityCheckService do +describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do shared_examples_for 'unmergeable merge request' do it 'updates or keeps merge status as cannot_be_merged' do subject @@ -60,24 +60,69 @@ describe MergeRequests::MergeabilityCheckService do subject { described_class.new(merge_request).execute } + def execute_within_threads(amount:, retry_lease: true) + threads = [] + + amount.times do + # Let's use a different object for each thread to get closer + # to a real world scenario. + mr = MergeRequest.find(merge_request.id) + + threads << Thread.new do + described_class.new(mr).execute(retry_lease: retry_lease) + end + end + + threads.each(&:join) + threads + end + before do project.add_developer(merge_request.author) end it_behaves_like 'mergeable merge request' - context 'when multiple calls to the service' do - it 'returns success' do - subject - result = subject + context 'when lock is disabled' do + before do + stub_feature_flags(merge_ref_auto_sync_lock: false) + end - expect(result).to be_a(ServiceResponse) - expect(result.success?).to be(true) + it_behaves_like 'mergeable merge request' + end + + context 'when concurrent calls' do + it 'waits first lock and returns "cached" result in subsequent calls' do + threads = execute_within_threads(amount: 3) + results = threads.map { |t| t.value.status } + + expect(results).to contain_exactly(:success, :success, :success) + end + + it 'writes the merge-ref once' do + service = instance_double(MergeRequests::MergeToRefService) + + expect(MergeRequests::MergeToRefService).to receive(:new).once { service } + expect(service).to receive(:execute).once.and_return(success: true) + + execute_within_threads(amount: 3) end - it 'second call does not change the merge-ref' do - expect { subject }.to change(merge_request, :merge_ref_head).from(nil) - expect { subject }.not_to change(merge_request.merge_ref_head, :id) + it 'resets one merge request upon execution' do + expect_any_instance_of(MergeRequest).to receive(:reset).once + + execute_within_threads(amount: 2) + end + + context 'when retry_lease flag is false' do + it 'the first call succeeds, subsequent concurrent calls get a lock error response' do + threads = execute_within_threads(amount: 3, retry_lease: false) + results = threads.map { |t| [t.value.status, t.value.message] } + + expect(results).to contain_exactly([:error, 'Failed to obtain a lock'], + [:error, 'Failed to obtain a lock'], + [:success, nil]) + end end end @@ -102,8 +147,7 @@ describe MergeRequests::MergeabilityCheckService do context 'when broken' do before do - allow(merge_request).to receive(:broken?) { true } - allow(project.repository).to receive(:can_be_merged?) { false } + expect(merge_request).to receive(:broken?) { true } end it_behaves_like 'unmergeable merge request' @@ -117,10 +161,13 @@ describe MergeRequests::MergeabilityCheckService do end end - context 'when it has conflicts' do - before do - allow(merge_request).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?) { false } + context 'when it cannot be merged on git' do + let(:merge_request) do + create(:merge_request, + merge_status: :unchecked, + source_branch: 'conflict-resolvable', + source_project: project, + target_branch: 'conflict-start') end it_behaves_like 'unmergeable merge request' diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 2e58da894e5..9688e02d6ac 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -91,7 +91,8 @@ describe MergeRequests::UpdateService, :mailer do labels: [], mentioned_users: [user2], assignees: [user3], - total_time_spent: 0 + total_time_spent: 0, + description: "FYI #{user2.to_reference}" } ) end diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb new file mode 100644 index 00000000000..803b9a93be7 --- /dev/null +++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:user) { create(:user) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + describe '#get_dashboard' do + let(:service_params) { [project, user, { environment: environment }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'valid embedded dashboard service response' + it_behaves_like 'raises error for users with insufficient permissions' + + it 'caches the unprocessed dashboard for subsequent calls' do + system_service = Metrics::Dashboard::SystemDashboardService + + expect(system_service).to receive(:new).once.and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'when called with a non-system dashboard' do + let(:dashboard_path) { 'garbage/dashboard/path' } + + it_behaves_like 'valid embedded dashboard service response' + end + end +end diff --git a/spec/services/metrics/dashboard/project_dashboard_service_spec.rb b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb new file mode 100644 index 00000000000..1357914be2a --- /dev/null +++ b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:user) { create(:user) } + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + describe 'get_dashboard' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + context 'when the dashboard does not exist' do + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + it_behaves_like 'raises error for users with insufficient permissions' + + context 'when the dashboard exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid dashboard service response' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect_any_instance_of(described_class) + .to receive(:get_raw_dashboard) + .once + .and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'and the dashboard is then deleted' do + it 'does not return the previously cached dashboard' do + described_class.new(*service_params).get_dashboard + + delete_project_dashboard(project, user, dashboard_path) + + expect_any_instance_of(described_class) + .to receive(:get_raw_dashboard) + .once + .and_call_original + + described_class.new(*service_params).get_dashboard + end + end + end + + context 'when the dashboard is configured incorrectly' do + let(:project) { project_with_dashboard(dashboard_path, {}) } + + it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity + end + end + + describe '::all_dashboard_paths' do + let(:all_dashboards) { described_class.all_dashboard_paths(project) } + + context 'when there are no project dashboards' do + it 'returns an empty array' do + expect(all_dashboards).to be_empty + end + end + + context 'when there are project dashboards available' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:project) { project_with_dashboard(dashboard_path) } + + it 'returns the dashboard attributes' do + expect(all_dashboards).to eq( + [{ + path: dashboard_path, + display_name: 'test.yml', + default: false + }] + ) + end + end + end +end diff --git a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb new file mode 100644 index 00000000000..8be3e7f6064 --- /dev/null +++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:user) { create(:user) } + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + describe 'get_dashboard' do + let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH } + let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'valid dashboard service response' + it_behaves_like 'raises error for users with insufficient permissions' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect(YAML).to receive(:safe_load).once.and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'when called with a non-system dashboard' do + let(:dashboard_path) { 'garbage/dashboard/path' } + + # We want to alwaus return the system dashboard. + it_behaves_like 'valid dashboard service response' + end + end + + describe '::all_dashboard_paths' do + it 'returns the dashboard attributes' do + all_dashboards = described_class.all_dashboard_paths(project) + + expect(all_dashboards).to eq( + [{ + path: described_class::SYSTEM_DASHBOARD_PATH, + display_name: described_class::SYSTEM_DASHBOARD_NAME, + default: true + }] + ) + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index c20de1fd079..1dcade1de0d 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -111,7 +111,7 @@ describe NotificationService, :mailer do should_email(participant) end - context 'for subgroups', :nested_groups do + context 'for subgroups' do before do build_group(project) end @@ -337,7 +337,7 @@ describe NotificationService, :mailer do it_behaves_like 'new note notifications' - context 'which is a subgroup', :nested_groups do + context 'which is a subgroup' do let!(:parent) { create(:group) } let!(:group) { create(:group, parent: parent) } @@ -388,7 +388,7 @@ describe NotificationService, :mailer do should_email(admin) end - context 'on project that belongs to subgroup', :nested_groups do + context 'on project that belongs to subgroup' do let(:group_reporter) { create(:user) } let(:group_guest) { create(:user) } let(:parent_group) { create(:group) } @@ -458,7 +458,7 @@ describe NotificationService, :mailer do should_not_email_nested_group_user(@pg_disabled) end - it 'notifies parent group members with mention level', :nested_groups do + it 'notifies parent group members with mention level' do note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}") notification.new_note(note) @@ -2410,52 +2410,40 @@ describe NotificationService, :mailer do group end - # Creates a nested group only if supported - # to avoid errors on MySQL def create_nested_group(visibility) - if Group.supports_nested_objects? - parent_group = create(:group, visibility) - child_group = create(:group, visibility, parent: parent_group) + parent_group = create(:group, visibility) + child_group = create(:group, visibility, parent: parent_group) - # Parent group member: global=disabled, parent_group=watch, child_group=global - @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group) - @pg_watcher.notification_settings_for(nil).disabled! + # Parent group member: global=disabled, parent_group=watch, child_group=global + @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group) + @pg_watcher.notification_settings_for(nil).disabled! - # Parent group member: global=global, parent_group=disabled, child_group=global - @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group) - @pg_disabled.notification_settings_for(nil).global! + # Parent group member: global=global, parent_group=disabled, child_group=global + @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group) + @pg_disabled.notification_settings_for(nil).global! - # Parent group member: global=global, parent_group=mention, child_group=global - @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group) - @pg_mention.notification_settings_for(nil).global! + # Parent group member: global=global, parent_group=mention, child_group=global + @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group) + @pg_mention.notification_settings_for(nil).global! - # Parent group member: global=global, parent_group=participating, child_group=global - @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group) - @pg_mention.notification_settings_for(nil).global! + # Parent group member: global=global, parent_group=participating, child_group=global + @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group) + @pg_mention.notification_settings_for(nil).global! - child_group - else - create(:group, visibility) - end + child_group end def add_member_for_parent_group(user, project) - return unless Group.supports_nested_objects? - project.reload project.group.parent.add_maintainer(user) end def should_email_nested_group_user(user, times: 1, recipients: email_recipients) - return unless Group.supports_nested_objects? - should_email(user, times: 1, recipients: email_recipients) end def should_not_email_nested_group_user(user, recipients: email_recipients) - return unless Group.supports_nested_objects? - should_not_email(user, recipients: email_recipients) end diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index 2f70c8ea94d..b625653bc77 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -118,7 +118,7 @@ describe Projects::AutocompleteService do expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title]) end - context 'with nested groups', :nested_groups do + context 'with nested groups' do let(:subgroup) { create(:group, :public, parent: group) } let!(:subgroup_milestone) { create(:milestone, group: subgroup) } diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 3af7ee3ad50..e436af77ed4 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -121,7 +121,22 @@ describe Projects::DestroyService do it { expect(Dir.exist?(remove_path)).to be_truthy } end - context 'when flushing caches fail' do + context 'when flushing caches fail due to Git errors' do + before do + allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError) + allow(Gitlab::GitLogger).to receive(:warn).with( + class: described_class.name, + project_id: project.id, + disk_path: project.disk_path, + message: 'Gitlab::Git::CommandError').and_call_original + + perform_enqueued_jobs { destroy_project(project, user, {}) } + end + + it_behaves_like 'deleting the project' + end + + context 'when flushing caches fail due to Redis' do before do new_user = create(:user) project.team.add_user(new_user, Gitlab::Access::DEVELOPER) diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index f25233ceeb1..06efc2ff825 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -20,13 +20,8 @@ describe Projects::DownloadService do context 'for URLs that are on the whitelist' do before do - sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub - sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg') - sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain') - end - - after do - ShamRack.unmount_all + stub_request(:get, 'http://mycompany.fogbugz.com/rails_sample.jpg').to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg')) + stub_request(:get, 'http://mycompany.fogbugz.com/doc_sample.txt').to_return(body: File.read(Rails.root + 'spec/fixtures/doc_sample.txt')) end context 'an image file' do diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index 75d534c59bf..970e82e7107 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -17,7 +17,7 @@ describe Projects::LfsPointers::LfsDownloadService do before do ApplicationSetting.create_from_defaults - stub_application_setting(allow_local_requests_from_hooks_and_services: local_request_setting) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: local_request_setting) allow(project).to receive(:lfs_enabled?).and_return(true) end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 71c4c3ad0d7..bf5f211b11c 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -28,61 +28,108 @@ describe QuickActions::InterpretService do shared_examples 'reopen command' do it 'returns state_event: "reopen" if content contains /reopen' do issuable.close! - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(state_event: 'reopen') end + + it 'returns the reopen message' do + issuable.close! + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Reopened this #{issuable.to_ability_name.humanize(capitalize: false)}.") + end end shared_examples 'close command' do it 'returns state_event: "close" if content contains /close' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(state_event: 'close') end + + it 'returns the close message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Closed this #{issuable.to_ability_name.humanize(capitalize: false)}.") + end end shared_examples 'title command' do it 'populates title: "A brand new title" if content contains /title A brand new title' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(title: 'A brand new title') end + + it 'returns the title message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq(%{Changed the title to "A brand new title".}) + end end shared_examples 'milestone command' do it 'fetches milestone and populates milestone_id if content contains /milestone' do milestone # populate the milestone - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(milestone_id: milestone.id) end + + it 'returns the milestone message' do + milestone # populate the milestone + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Set the milestone to #{milestone.to_reference}.") + end + + it 'returns empty milestone message when milestone is wrong' do + _, _, message = service.execute('/milestone %wrong-milestone', issuable) + + expect(message).to be_empty + end end shared_examples 'remove_milestone command' do it 'populates milestone_id: nil if content contains /remove_milestone' do issuable.update!(milestone_id: milestone.id) - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(milestone_id: nil) end + + it 'returns removed milestone message' do + issuable.update!(milestone_id: milestone.id) + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Removed #{milestone.to_reference} milestone.") + end end shared_examples 'label command' do it 'fetches label ids and populates add_label_ids if content contains /label' do bug # populate the label inprogress # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(add_label_ids: [bug.id, inprogress.id]) end + + it 'returns the label message' do + bug # populate the label + inprogress # populate the label + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Added #{bug.to_reference(format: :name)} #{inprogress.to_reference(format: :name)} labels.") + end end shared_examples 'multiple label command' do it 'fetches label ids and populates add_label_ids if content contains multiple /label' do bug # populate the label inprogress # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) end @@ -91,7 +138,7 @@ describe QuickActions::InterpretService do shared_examples 'multiple label with same argument' do it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do inprogress # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(add_label_ids: [inprogress.id]) end @@ -120,16 +167,23 @@ describe QuickActions::InterpretService do shared_examples 'unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do issuable.update!(label_ids: [inprogress.id]) # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(remove_label_ids: [inprogress.id]) end + + it 'returns the unlabel message' do + issuable.update!(label_ids: [inprogress.id]) # populate the label + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Removed #{inprogress.to_reference(format: :name)} label.") + end end shared_examples 'multiple unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do issuable.update!(label_ids: [inprogress.id, bug.id]) # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) end @@ -138,7 +192,7 @@ describe QuickActions::InterpretService do shared_examples 'unlabel command with no argument' do it 'populates label_ids: [] if content contains /unlabel with no arguments' do issuable.update!(label_ids: [inprogress.id]) # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(label_ids: []) end @@ -148,91 +202,161 @@ describe QuickActions::InterpretService do it 'populates label_ids: [] if content contains /relabel' do issuable.update!(label_ids: [bug.id]) # populate the label inprogress # populate the label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(label_ids: [inprogress.id]) end + + it 'returns the relabel message' do + issuable.update!(label_ids: [bug.id]) # populate the label + inprogress # populate the label + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Replaced all labels with #{inprogress.to_reference(format: :name)} label.") + end end shared_examples 'todo command' do it 'populates todo_event: "add" if content contains /todo' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(todo_event: 'add') end + + it 'returns the todo message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Added a todo.') + end end shared_examples 'done command' do it 'populates todo_event: "done" if content contains /done' do TodoService.new.mark_todo(issuable, developer) - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(todo_event: 'done') end + + it 'returns the done message' do + TodoService.new.mark_todo(issuable, developer) + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Marked to do as done.') + end end shared_examples 'subscribe command' do it 'populates subscription_event: "subscribe" if content contains /subscribe' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(subscription_event: 'subscribe') end + + it 'returns the subscribe message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Subscribed to this #{issuable.to_ability_name.humanize(capitalize: false)}.") + end end shared_examples 'unsubscribe command' do it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do issuable.subscribe(developer, project) - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(subscription_event: 'unsubscribe') end + + it 'returns the unsubscribe message' do + issuable.subscribe(developer, project) + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Unsubscribed from this #{issuable.to_ability_name.humanize(capitalize: false)}.") + end end shared_examples 'due command' do + let(:expected_date) { Date.new(2016, 8, 28) } + it 'populates due_date: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) + + expect(updates).to eq(due_date: expected_date) + end - expect(updates).to eq(due_date: defined?(expected_date) ? expected_date : Date.new(2016, 8, 28)) + it 'returns due_date message: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Set the due date to #{expected_date.to_s(:medium)}.") end end shared_examples 'remove_due_date command' do - it 'populates due_date: nil if content contains /remove_due_date' do + before do issuable.update!(due_date: Date.today) - _, updates = service.execute(content, issuable) + end + + it 'populates due_date: nil if content contains /remove_due_date' do + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(due_date: nil) end + + it 'returns Removed the due date' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Removed the due date.') + end end shared_examples 'wip command' do it 'returns wip_event: "wip" if content contains /wip' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(wip_event: 'wip') end + + it 'returns the wip message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Marked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.") + end end shared_examples 'unwip command' do it 'returns wip_event: "unwip" if content contains /wip' do issuable.update!(title: issuable.wip_title) - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(wip_event: 'unwip') end + + it 'returns the unwip message' do + issuable.update!(title: issuable.wip_title) + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Unmarked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.") + end end shared_examples 'estimate command' do it 'populates time_estimate: 3600 if content contains /estimate 1h' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(time_estimate: 3600) end + + it 'returns the time_estimate formatted message' do + _, _, message = service.execute('/estimate 79d', issuable) + + expect(message).to eq('Set time estimate to 3mo 3w 4d.') + end end shared_examples 'spend command' do it 'populates spend_time: 3600 if content contains /spend 1h' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(spend_time: { duration: 3600, @@ -240,11 +364,17 @@ describe QuickActions::InterpretService do spent_at: DateTime.now.to_date }) end + + it 'returns the spend_time message including the formatted duration and verb' do + _, _, message = service.execute('/spend -120m', issuable) + + expect(message).to eq('Subtracted 2h spent time.') + end end shared_examples 'spend command with negative time' do it 'populates spend_time: -1800 if content contains /spend -30m' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(spend_time: { duration: -1800, @@ -256,7 +386,7 @@ describe QuickActions::InterpretService do shared_examples 'spend command with valid date' do it 'populates spend time: 1800 with date in date type format' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(spend_time: { duration: 1800, @@ -268,7 +398,7 @@ describe QuickActions::InterpretService do shared_examples 'spend command with invalid date' do it 'will not create any note and timelog' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq({}) end @@ -276,7 +406,7 @@ describe QuickActions::InterpretService do shared_examples 'spend command with future date' do it 'will not create any note and timelog' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq({}) end @@ -284,18 +414,30 @@ describe QuickActions::InterpretService do shared_examples 'remove_estimate command' do it 'populates time_estimate: 0 if content contains /remove_estimate' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(time_estimate: 0) end + + it 'returns the remove_estimate message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Removed time estimate.') + end end shared_examples 'remove_time_spent command' do it 'populates spend_time: :reset if content contains /remove_time_spent' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(spend_time: { duration: :reset, user_id: developer.id }) end + + it 'returns the remove_time_spent message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Removed spent time.') + end end shared_examples 'lock command' do @@ -303,10 +445,16 @@ describe QuickActions::InterpretService do let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: false) } it 'returns discussion_locked: true if content contains /lock' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(discussion_locked: true) end + + it 'returns the lock discussion message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Locked the discussion') + end end shared_examples 'unlock command' do @@ -314,45 +462,79 @@ describe QuickActions::InterpretService do let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: true) } it 'returns discussion_locked: true if content contains /unlock' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(discussion_locked: false) end + + it 'returns the unlock discussion message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Unlocked the discussion') + end end - shared_examples 'empty command' do + shared_examples 'empty command' do |error_msg| it 'populates {} if content contains an unsupported command' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to be_empty end + + it "returns #{error_msg || 'an empty'} message" do + _, _, message = service.execute(content, issuable) + + if error_msg + expect(message).to eq(error_msg) + else + expect(message).to be_empty + end + end end shared_examples 'merge command' do let(:project) { create(:project, :repository) } it 'runs merge command if content contains /merge' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(merge: merge_request.diff_head_sha) end + + it 'returns them merge message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Scheduled to merge this merge request when the pipeline succeeds.') + end end shared_examples 'award command' do it 'toggle award 100 emoji if content contains /award :100:' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(emoji_award: "100") end + + it 'returns the award message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Toggled :100: emoji award.') + end end shared_examples 'duplicate command' do it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do issue_duplicate # populate the issue - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(canonical_issue_id: issue_duplicate.id) end + + it 'returns the duplicate message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Marked this issue as a duplicate of #{issue_duplicate.to_reference(project)}.") + end end shared_examples 'copy_metadata command' do @@ -360,7 +542,7 @@ describe QuickActions::InterpretService do source_issuable # populate the issue todo_label # populate this label inreview_label # populate this label - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates[:add_label_ids]).to match_array([inreview_label.id, todo_label.id]) @@ -370,19 +552,45 @@ describe QuickActions::InterpretService do expect(updates).not_to have_key(:milestone_id) end end + + it 'returns the copy metadata message' do + _, _, message = service.execute("/copy_metadata #{source_issuable.to_reference}", issuable) + + expect(message).to eq("Copied labels and milestone from #{source_issuable.to_reference}.") + end + end + + describe 'move issue command' do + it 'returns the move issue message' do + _, _, message = service.execute("/move #{project.full_path}", issue) + + expect(message).to eq("Moved this issue to #{project.full_path}.") + end + + it 'returns move issue failure message when the referenced issue is not found' do + _, _, message = service.execute('/move invalid', issue) + + expect(message).to eq("Move this issue failed because target project doesn't exists") + end end shared_examples 'confidential command' do it 'marks issue as confidential if content contains /confidential' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(confidential: true) end + + it 'returns the confidential message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq('Made this issue confidential') + end end shared_examples 'shrug command' do it 'appends ¯\_(ツ)_/¯ to the comment' do - new_content, _ = service.execute(content, issuable) + new_content, _, _ = service.execute(content, issuable) expect(new_content).to end_with(described_class::SHRUG) end @@ -390,7 +598,7 @@ describe QuickActions::InterpretService do shared_examples 'tableflip command' do it 'appends (╯°□°)╯︵ ┻━┻ to the comment' do - new_content, _ = service.execute(content, issuable) + new_content, _, _ = service.execute(content, issuable) expect(new_content).to end_with(described_class::TABLEFLIP) end @@ -398,18 +606,34 @@ describe QuickActions::InterpretService do shared_examples 'tag command' do it 'tags a commit' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(tag_name: tag_name, tag_message: tag_message) end + + it 'returns the tag message' do + _, _, message = service.execute(content, issuable) + + if tag_message.present? + expect(message).to eq(%{Tagged this commit to #{tag_name} with "#{tag_message}".}) + else + expect(message).to eq("Tagged this commit to #{tag_name}.") + end + end end shared_examples 'assign command' do it 'assigns to a single user' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(assignee_ids: [developer.id]) end + + it 'returns the assign message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Assigned #{developer.to_reference}.") + end end it_behaves_like 'reopen command' do @@ -463,7 +687,7 @@ describe QuickActions::InterpretService do let(:service) { described_class.new(project, developer, {}) } it 'precheck passes and returns merge command' do - _, updates = service.execute('/merge', merge_request) + _, updates, _ = service.execute('/merge', merge_request) expect(updates).to eq(merge: nil) end @@ -559,7 +783,7 @@ describe QuickActions::InterpretService do end end - it_behaves_like 'empty command' do + it_behaves_like 'empty command', "Assign command failed because no user was found" do let(:content) { '/assign @abcd1234' } let(:issuable) { issue } end @@ -575,19 +799,33 @@ describe QuickActions::InterpretService do context 'Issue' do it 'populates assignee_ids: [] if content contains /unassign' do issue.update!(assignee_ids: [developer.id]) - _, updates = service.execute(content, issue) + _, updates, _ = service.execute(content, issue) expect(updates).to eq(assignee_ids: []) end + + it 'returns the unassign message for all the assignee if content contains /unassign' do + issue.update(assignee_ids: [developer.id, developer2.id]) + _, _, message = service.execute(content, issue) + + expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.") + end end context 'Merge Request' do it 'populates assignee_ids: [] if content contains /unassign' do merge_request.update!(assignee_ids: [developer.id]) - _, updates = service.execute(content, merge_request) + _, updates, _ = service.execute(content, merge_request) expect(updates).to eq(assignee_ids: []) end + + it 'returns the unassign message for all the assignee if content contains /unassign' do + merge_request.update(assignee_ids: [developer.id, developer2.id]) + _, _, message = service.execute(content, merge_request) + + expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.") + end end end @@ -979,12 +1217,12 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end - it_behaves_like 'empty command' do + it_behaves_like 'empty command', 'Mark as duplicate failed because referenced issue was not found' do let(:content) { "/duplicate imaginary#1234" } let(:issuable) { issue } end - it_behaves_like 'empty command' do + it_behaves_like 'empty command', 'Mark as duplicate failed because referenced issue was not found' do let(:other_project) { create(:project, :private) } let(:issue_duplicate) { create(:issue, project: other_project) } @@ -1049,7 +1287,7 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end - it_behaves_like 'empty command' do + it_behaves_like 'empty command', 'Mark as duplicate failed because referenced issue was not found' do let(:content) { '/duplicate #{issue.to_reference}' } let(:issuable) { issue } end @@ -1132,13 +1370,13 @@ describe QuickActions::InterpretService do let(:service) { described_class.new(non_empty_project, developer)} it 'updates target_branch if /target_branch command is executed' do - _, updates = service.execute('/target_branch merge-test', merge_request) + _, updates, _ = service.execute('/target_branch merge-test', merge_request) expect(updates).to eq(target_branch: 'merge-test') end it 'handles blanks around param' do - _, updates = service.execute('/target_branch merge-test ', merge_request) + _, updates, _ = service.execute('/target_branch merge-test ', merge_request) expect(updates).to eq(target_branch: 'merge-test') end @@ -1156,6 +1394,12 @@ describe QuickActions::InterpretService do let(:issuable) { another_merge_request } end end + + it 'returns the target_branch message' do + _, _, message = service.execute('/target_branch merge-test', merge_request) + + expect(message).to eq('Set target branch to merge-test.') + end end context '/board_move command' do @@ -1171,13 +1415,13 @@ describe QuickActions::InterpretService do it 'populates remove_label_ids for all current board columns' do issue.update!(label_ids: [todo.id, inprogress.id]) - _, updates = service.execute(content, issue) + _, updates, _ = service.execute(content, issue) expect(updates[:remove_label_ids]).to match_array([todo.id, inprogress.id]) end it 'populates add_label_ids with the id of the given label' do - _, updates = service.execute(content, issue) + _, updates, _ = service.execute(content, issue) expect(updates[:add_label_ids]).to eq([inreview.id]) end @@ -1185,7 +1429,7 @@ describe QuickActions::InterpretService do it 'does not include the given label id in remove_label_ids' do issue.update!(label_ids: [todo.id, inreview.id]) - _, updates = service.execute(content, issue) + _, updates, _ = service.execute(content, issue) expect(updates[:remove_label_ids]).to match_array([todo.id]) end @@ -1193,11 +1437,19 @@ describe QuickActions::InterpretService do it 'does not remove label ids that are not lists on the board' do issue.update!(label_ids: [todo.id, bug.id]) - _, updates = service.execute(content, issue) + _, updates, _ = service.execute(content, issue) expect(updates[:remove_label_ids]).to match_array([todo.id]) end + it 'returns board_move message' do + issue.update!(label_ids: [todo.id, inprogress.id]) + + _, _, message = service.execute(content, issue) + + expect(message).to eq("Moved issue to ~#{inreview.id} column in the board.") + end + context 'if the project has multiple boards' do let(:issuable) { issue } @@ -1211,13 +1463,13 @@ describe QuickActions::InterpretService do context 'if the given label does not exist' do let(:issuable) { issue } let(:content) { '/board_move ~"Fake Label"' } - it_behaves_like 'empty command' + it_behaves_like 'empty command', 'Move this issue failed because you need to specify only one label.' end context 'if multiple labels are given' do let(:issuable) { issue } let(:content) { %{/board_move ~"#{inreview.title}" ~"#{todo.title}"} } - it_behaves_like 'empty command' + it_behaves_like 'empty command', 'Move this issue failed because you need to specify only one label.' end context 'if the given label is not a list on the board' do @@ -1292,10 +1544,16 @@ describe QuickActions::InterpretService do end it 'populates create_merge_request with branch_name and issue iid' do - _, updates = service.execute(content, issuable) + _, updates, _ = service.execute(content, issuable) expect(updates).to eq(create_merge_request: { branch_name: branch_name, issue_iid: issuable.iid }) end + + it 'returns the create_merge_request message' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Created branch '#{branch_name}' and a merge request to resolve this issue") + end end end @@ -1558,6 +1816,12 @@ describe QuickActions::InterpretService do expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue']) end + + it 'returns the execution message using the default branch name' do + _, _, message = service.execute(content, issue) + + expect(message).to eq('Created a branch and a merge request to resolve this issue') + end end context 'with a branch name' do @@ -1568,6 +1832,12 @@ describe QuickActions::InterpretService do expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue"]) end + + it 'returns the execution message using the given branch name' do + _, _, message = service.execute(content, issue) + + expect(message).to eq("Created branch 'foo' and a merge request to resolve this issue") + end end end diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb index d11e27c6d52..7d4faba526b 100644 --- a/spec/services/self_monitoring/project/create_service_spec.rb +++ b/spec/services/self_monitoring/project/create_service_spec.rb @@ -37,7 +37,7 @@ describe SelfMonitoring::Project::CreateService do allow(ApplicationSetting) .to receive(:current) .and_return( - ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true) + ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: true) ) end @@ -90,19 +90,17 @@ describe SelfMonitoring::Project::CreateService do ) end - # This should pass when https://gitlab.com/gitlab-org/gitlab-ce/issues/44496 - # is complete and the prometheus listen address is added to the whitelist. - # context 'when local requests from hooks and services are not allowed' do - # before do - # allow(ApplicationSetting) - # .to receive(:current) - # .and_return( - # ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: false) - # ) - # end - - # it_behaves_like 'has prometheus service', 'http://localhost:9090' - # end + context 'when local requests from hooks and services are not allowed' do + before do + allow(ApplicationSetting) + .to receive(:current) + .and_return( + ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: false) + ) + end + + it_behaves_like 'has prometheus service', 'http://localhost:9090' + end context 'with non default prometheus address' do before do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 157cfc46e69..486d0ca0c56 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -513,6 +513,30 @@ describe SystemNoteService do end end + describe '.zoom_link_added' do + subject { described_class.zoom_link_added(issue, project, author) } + + it_behaves_like 'a system note' do + let(:action) { 'pinned_embed' } + end + + it 'sets the zoom link added note text' do + expect(subject.note).to eq('a Zoom call was added to this issue') + end + end + + describe '.zoom_link_removed' do + subject { described_class.zoom_link_removed(issue, project, author) } + + it_behaves_like 'a system note' do + let(:action) { 'pinned_embed' } + end + + it 'sets the zoom link removed note text' do + expect(subject.note).to eq('a Zoom call was removed from this issue') + end + end + describe '.cross_reference' do subject { described_class.cross_reference(noteable, mentioner, author) } diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb index 2a553e18807..ce809bbf6c5 100644 --- a/spec/services/todos/destroy/entity_leave_service_spec.rb +++ b/spec/services/todos/destroy/entity_leave_service_spec.rb @@ -176,7 +176,7 @@ describe Todos::Destroy::EntityLeaveService do end end - context 'with nested groups', :nested_groups do + context 'with nested groups' do let(:subgroup) { create(:group, :private, parent: group) } let(:subgroup2) { create(:group, :private, parent: group) } let(:subproject) { create(:project, group: subgroup) } diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb index a1798686d7c..7dd495847b3 100644 --- a/spec/services/todos/destroy/group_private_service_spec.rb +++ b/spec/services/todos/destroy/group_private_service_spec.rb @@ -35,7 +35,7 @@ describe Todos::Destroy::GroupPrivateService do expect(project_member.todos).to match_array([todo_project_member]) end - context 'with nested groups', :nested_groups do + context 'with nested groups' do let(:parent_group) { create(:group) } let(:subgroup) { create(:group, :private, parent: group) } let(:subproject) { create(:project, group: subgroup) } diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index 0287a24808d..f5a914bb482 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -135,7 +135,7 @@ describe Users::RefreshAuthorizedProjectsService do end end - context 'projects of subgroups of groups the user is a member of', :nested_groups do + context 'projects of subgroups of groups the user is a member of' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } let!(:other_project) { create(:project, group: nested_group) } @@ -163,7 +163,7 @@ describe Users::RefreshAuthorizedProjectsService do end end - context 'projects shared with subgroups of groups the user is a member of', :nested_groups do + context 'projects shared with subgroups of groups the user is a member of' do let(:group) { create(:group) } let(:nested_group) { create(:group, parent: group) } let(:other_project) { create(:project) } diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 37bafc0c002..50167a2e059 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -19,17 +19,37 @@ describe WebHookService do let(:service_instance) { described_class.new(project_hook, data, :push_hooks) } describe '#initialize' do - it 'allow_local_requests is true if hook is a SystemHook' do - instance = described_class.new(build(:system_hook), data, :system_hook) - expect(instance.request_options[:allow_local_requests]).to be_truthy + before do + stub_application_setting(setting_name => setting) end - it 'allow_local_requests is false if hook is not a SystemHook' do - %i(project_hook service_hook web_hook_log).each do |hook| - instance = described_class.new(build(hook), data, hook) - expect(instance.request_options[:allow_local_requests]).to be_falsey + shared_examples_for 'respects outbound network setting' do + context 'when local requests are allowed' do + let(:setting) { true } + + it { expect(hook.request_options[:allow_local_requests]).to be_truthy } + end + + context 'when local requests are not allowed' do + let(:setting) { false } + + it { expect(hook.request_options[:allow_local_requests]).to be_falsey } end end + + context 'when SystemHook' do + let(:setting_name) { :allow_local_requests_from_system_hooks } + let(:hook) { described_class.new(build(:system_hook), data, :system_hook) } + + include_examples 'respects outbound network setting' + end + + context 'when ProjectHook' do + let(:setting_name) { :allow_local_requests_from_web_hooks_and_services } + let(:hook) { described_class.new(build(:project_hook), data, :project_hook) } + + include_examples 'respects outbound network setting' + end end describe '#execute' do diff --git a/spec/services/zoom_notes_service_spec.rb b/spec/services/zoom_notes_service_spec.rb new file mode 100644 index 00000000000..419ecf3f374 --- /dev/null +++ b/spec/services/zoom_notes_service_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ZoomNotesService do + describe '#execute' do + let(:issue) { OpenStruct.new(description: description) } + let(:project) { Object.new } + let(:user) { Object.new } + let(:description) { 'an issue description' } + let(:old_description) { nil } + + subject { described_class.new(issue, project, user, old_description: old_description) } + + shared_examples 'no notifications' do + it "doesn't create notifications" do + expect(SystemNoteService).not_to receive(:zoom_link_added) + expect(SystemNoteService).not_to receive(:zoom_link_removed) + + subject.execute + end + end + + it_behaves_like 'no notifications' + + context 'when the zoom link exists in both description and old_description' do + let(:description) { 'a changed issue description https://zoom.us/j/123' } + let(:old_description) { 'an issue description https://zoom.us/j/123' } + + it_behaves_like 'no notifications' + end + + context "when the zoom link doesn't exist in both description and old_description" do + let(:description) { 'a changed issue description' } + let(:old_description) { 'an issue description' } + + it_behaves_like 'no notifications' + end + + context 'when description == old_description' do + let(:old_description) { 'an issue description' } + + it_behaves_like 'no notifications' + end + + context 'when the description contains a zoom link and old_description is nil' do + let(:description) { 'a changed issue description https://zoom.us/j/123' } + + it 'creates a zoom_link_added notification' do + expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user) + expect(SystemNoteService).not_to receive(:zoom_link_removed) + + subject.execute + end + end + + context 'when the zoom link has been added to the description' do + let(:description) { 'a changed issue description https://zoom.us/j/123' } + let(:old_description) { 'an issue description' } + + it 'creates a zoom_link_added notification' do + expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user) + expect(SystemNoteService).not_to receive(:zoom_link_removed) + + subject.execute + end + end + + context 'when the zoom link has been removed from the description' do + let(:description) { 'a changed issue description' } + let(:old_description) { 'an issue description https://zoom.us/j/123' } + + it 'creates a zoom_link_removed notification' do + expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user) + expect(SystemNoteService).to receive(:zoom_link_removed) + + subject.execute + end + end + end +end |