diff options
author | Marcia Ramos <virtua.creative@gmail.com> | 2018-03-09 12:36:26 -0300 |
---|---|---|
committer | Marcia Ramos <virtua.creative@gmail.com> | 2018-03-09 12:36:26 -0300 |
commit | 5596933b535d632cf3c8159889a72b1e98e4ec0a (patch) | |
tree | 5edc39c0408a1e5bcbc13168dedbdabd1eba417f /spec/support | |
parent | da5694c5cbaf62d5568339efd1a6f340f97e6e53 (diff) | |
parent | 3bbe60f8e802ce3d9da060a47b7f635dedba7370 (diff) | |
download | gitlab-ce-docs-refactor-dev-guides.tar.gz |
fix conflictdocs-refactor-dev-guides
Diffstat (limited to 'spec/support')
22 files changed, 965 insertions, 142 deletions
diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb index 38d11992dc2..8eeaa37d3c5 100644 --- a/spec/support/bare_repo_operations.rb +++ b/spec/support/bare_repo_operations.rb @@ -11,6 +11,14 @@ class BareRepoOperations @path_to_repo = path_to_repo end + def commit_tree(tree_id, msg, parent: EMPTY_TREE_ID) + commit_tree_args = ['commit-tree', tree_id, '-m', msg] + commit_tree_args += ['-p', parent] unless parent == EMPTY_TREE_ID + commit_id = execute(commit_tree_args) + + commit_id[0] + end + # Based on https://stackoverflow.com/a/25556917/1856239 def commit_file(file, dst_path, branch = 'master') head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID @@ -26,11 +34,9 @@ class BareRepoOperations tree_id = execute(['write-tree']) - commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"] - commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID - commit_id = execute(commit_tree_args) + commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id) - execute(['update-ref', "refs/heads/#{branch}", commit_id[0]]) + execute(['update-ref', "refs/heads/#{branch}", commit_id]) end private diff --git a/spec/support/cluster_application_spec.rb b/spec/support/cluster_application_spec.rb deleted file mode 100644 index ab77910a050..00000000000 --- a/spec/support/cluster_application_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -shared_examples 'cluster application specs' do - let(:factory_name) { described_class.to_s.downcase.gsub("::", "_") } - - describe '#name' do - it 'is .application_name' do - expect(subject.name).to eq(described_class.application_name) - end - - it 'is recorded in Clusters::Cluster::APPLICATIONS' do - expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class) - end - end - - describe '#status' do - let(:cluster) { create(:cluster, :provided_by_gcp) } - - subject { described_class.new(cluster: cluster) } - - it 'defaults to :not_installable' do - expect(subject.status_name).to be(:not_installable) - end - - context 'when application helm is scheduled' do - before do - create(factory_name, :scheduled, cluster: cluster) - end - - it 'defaults to :not_installable' do - expect(subject.status_name).to be(:not_installable) - end - end - - context 'when application helm is installed' do - before do - create(:clusters_applications_helm, :installed, cluster: cluster) - end - - it 'defaults to :installable' do - expect(subject.status_name).to be(:installable) - end - end - end - - describe '#install_command' do - it 'has all the needed information' do - expect(subject.install_command).to have_attributes(name: subject.name, install_helm: false) - end - end - - describe 'status state machine' do - describe '#make_installing' do - subject { create(factory_name, :scheduled) } - - it 'is installing' do - subject.make_installing! - - expect(subject).to be_installing - end - end - - describe '#make_installed' do - subject { create(factory_name, :installing) } - - it 'is installed' do - subject.make_installed - - expect(subject).to be_installed - end - end - - describe '#make_errored' do - subject { create(factory_name, :installing) } - let(:reason) { 'some errors' } - - it 'is errored' do - subject.make_errored(reason) - - expect(subject).to be_errored - expect(subject.status_reason).to eq(reason) - end - end - - describe '#make_scheduled' do - subject { create(factory_name, :installable) } - - it 'is scheduled' do - subject.make_scheduled - - expect(subject).to be_scheduled - end - - describe 'when was errored' do - subject { create(factory_name, :errored) } - - it 'clears #status_reason' do - expect(subject.status_reason).not_to be_nil - - subject.make_scheduled! - - expect(subject.status_reason).to be_nil - end - end - end - end -end diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb index d5ef80cfab2..73cc64c0b74 100644 --- a/spec/support/cycle_analytics_helpers.rb +++ b/spec/support/cycle_analytics_helpers.rb @@ -26,7 +26,19 @@ module CycleAnalyticsHelpers ref: 'refs/heads/master').execute end - def create_merge_request_closing_issue(issue, message: nil, source_branch: nil, commit_message: 'commit message') + def create_cycle(user, project, issue, mr, milestone, pipeline) + issue.update(milestone: milestone) + pipeline.run + + ci_build = create(:ci_build, pipeline: pipeline, status: :success, author: user) + + merge_merge_requests_closing_issue(user, project, issue) + ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash) + + ci_build + end + + def create_merge_request_closing_issue(user, project, issue, message: nil, source_branch: nil, commit_message: 'commit message') if !source_branch || project.repository.commit(source_branch).blank? source_branch = generate(:branch) project.repository.add_branch(user, source_branch, 'master') @@ -52,19 +64,19 @@ module CycleAnalyticsHelpers mr end - def merge_merge_requests_closing_issue(issue) + def merge_merge_requests_closing_issue(user, project, issue) merge_requests = issue.closed_by_merge_requests(user) merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user).execute(merge_request) } end - def deploy_master(environment: 'production') + def deploy_master(user, project, environment: 'production') dummy_job = case environment when 'production' - dummy_production_job + dummy_production_job(user, project) when 'staging' - dummy_staging_job + dummy_staging_job(user, project) else raise ArgumentError end @@ -72,25 +84,24 @@ module CycleAnalyticsHelpers CreateDeploymentService.new(dummy_job).execute end - def dummy_production_job - @dummy_job ||= new_dummy_job('production') + def dummy_production_job(user, project) + new_dummy_job(user, project, 'production') end - def dummy_staging_job - @dummy_job ||= new_dummy_job('staging') + def dummy_staging_job(user, project) + new_dummy_job(user, project, 'staging') end - def dummy_pipeline - @dummy_pipeline ||= - Ci::Pipeline.new( - sha: project.repository.commit('master').sha, - ref: 'master', - source: :push, - project: project, - protected: false) + def dummy_pipeline(project) + Ci::Pipeline.new( + sha: project.repository.commit('master').sha, + ref: 'master', + source: :push, + project: project, + protected: false) end - def new_dummy_job(environment) + def new_dummy_job(user, project, environment) project.environments.find_or_create_by(name: environment) Ci::Build.new( @@ -101,7 +112,7 @@ module CycleAnalyticsHelpers tag: false, name: 'dummy', stage: 'dummy', - pipeline: dummy_pipeline, + pipeline: dummy_pipeline(project), protected: false) end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 2c20821ac3f..f61469f673d 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -127,7 +127,6 @@ shared_examples 'issuable record that supports quick actions in its description it "does not close the #{issuable_type}" do write_note("/close") - expect(page).to have_content '/close' expect(page).not_to have_content 'Commands applied' expect(issuable).to be_open @@ -165,7 +164,6 @@ shared_examples 'issuable record that supports quick actions in its description it "does not reopen the #{issuable_type}" do write_note("/reopen") - expect(page).to have_content '/reopen' expect(page).not_to have_content 'Commands applied' expect(issuable).to be_closed @@ -195,10 +193,9 @@ shared_examples 'issuable record that supports quick actions in its description visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end - it "does not reopen the #{issuable_type}" do + it "does not change the #{issuable_type} title" do write_note("/title Awesome new title") - expect(page).to have_content '/title' expect(page).not_to have_content 'Commands applied' expect(issuable.reload.title).not_to eq 'Awesome new title' diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb index 0d8f7a7aae6..f7f851eb1eb 100644 --- a/spec/support/features/variable_list_shared_examples.rb +++ b/spec/support/features/variable_list_shared_examples.rb @@ -261,6 +261,8 @@ shared_examples 'variable list' do click_button('Save variables') wait_for_requests + expect(all('.js-ci-variable-list-section .js-ci-variable-error-box ul li').count).to eq(1) + # We check the first row because it re-sorts to alphabetical order on refresh page.within('.js-ci-variable-list-section') do expect(find('.js-ci-variable-error-box')).to have_content(/Validation failed Variables have duplicate values \(.+\)/) diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs index 507e4ce785a..ea50e4ad3f6 100644 --- a/spec/support/gitlab-git-test.git/packed-refs +++ b/spec/support/gitlab-git-test.git/packed-refs @@ -1,4 +1,4 @@ -# pack-refs with: peeled fully-peeled +# pack-refs with: peeled fully-peeled sorted 0b4bc9a49b562e85de7cc9e834518ea6828729b9 refs/heads/feature 12d65c8dd2b2676fa3ac47d955accc085a37a9c1 refs/heads/fix 6473c90867124755509e100d0d35ebdc85a0b6ae refs/heads/fix-blob-path diff --git a/spec/support/gitlab_verify.rb b/spec/support/gitlab_verify.rb new file mode 100644 index 00000000000..13e2e37624d --- /dev/null +++ b/spec/support/gitlab_verify.rb @@ -0,0 +1,45 @@ +RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do + describe 'batching' do + let(:first_batch) { objects[0].id..objects[0].id } + let(:second_batch) { objects[1].id..objects[1].id } + let(:third_batch) { objects[2].id..objects[2].id } + + it 'iterates through objects in batches' do + expect(collect_ranges).to eq([first_batch, second_batch, third_batch]) + end + + it 'allows the starting ID to be specified' do + expect(collect_ranges(start: second_batch.first)).to eq([second_batch, third_batch]) + end + + it 'allows the finishing ID to be specified' do + expect(collect_ranges(finish: second_batch.last)).to eq([first_batch, second_batch]) + end + end +end + +module GitlabVerifyHelpers + def collect_ranges(args = {}) + verifier = described_class.new(args.merge(batch_size: 1)) + + collect_results(verifier).map { |range, _| range } + end + + def collect_failures + verifier = described_class.new(batch_size: 1) + + out = {} + + collect_results(verifier).map { |_, failures| out.merge!(failures) } + + out + end + + def collect_results(verifier) + out = [] + + verifier.run_batches { |*args| out << args } + + out + end +end diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb index 28d39a32f02..081ce0ad7b7 100644 --- a/spec/support/ldap_helpers.rb +++ b/spec/support/ldap_helpers.rb @@ -1,13 +1,13 @@ module LdapHelpers def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap)) - ::Gitlab::LDAP::Adapter.new(provider, ldap) + ::Gitlab::Auth::LDAP::Adapter.new(provider, ldap) end def user_dn(uid) "uid=#{uid},ou=users,dc=example,dc=com" end - # Accepts a hash of Gitlab::LDAP::Config keys and values. + # Accepts a hash of Gitlab::Auth::LDAP::Config keys and values. # # Example: # stub_ldap_config( @@ -15,21 +15,21 @@ module LdapHelpers # admin_group: 'my-admin-group' # ) def stub_ldap_config(messages) - allow_any_instance_of(::Gitlab::LDAP::Config).to receive_messages(messages) + allow_any_instance_of(::Gitlab::Auth::LDAP::Config).to receive_messages(messages) end # Stub an LDAP person search and provide the return entry. Specify `nil` for # `entry` to simulate when an LDAP person is not found # # Example: - # adapter = ::Gitlab::LDAP::Adapter.new('ldapmain', double(:ldap)) + # adapter = ::Gitlab::Auth::LDAP::Adapter.new('ldapmain', double(:ldap)) # ldap_user_entry = ldap_user_entry('john_doe') # # stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter) def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain') - return_value = ::Gitlab::LDAP::Person.new(entry, provider) if entry.present? + return_value = ::Gitlab::Auth::LDAP::Person.new(entry, provider) if entry.present? - allow(::Gitlab::LDAP::Person) + allow(::Gitlab::Auth::LDAP::Person) .to receive(:find_by_uid).with(uid, any_args).and_return(return_value) end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index b52b6a28c54..d08183846a0 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -138,7 +138,7 @@ module LoginHelpers Rails.application.routes.draw do post '/users/auth/saml' => 'omniauth_callbacks#saml' end - allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) + allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config) stub_omniauth_setting(messages) allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml') allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') @@ -149,10 +149,10 @@ module LoginHelpers end def stub_basic_saml_config - allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) + allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } }) end def stub_saml_group_config(groups) - allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) + allow(Gitlab::Auth::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } }) end end diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb new file mode 100644 index 00000000000..d8424405b96 --- /dev/null +++ b/spec/support/matchers/match_ids.rb @@ -0,0 +1,24 @@ +RSpec::Matchers.define :match_ids do |*expected| + match do |actual| + actual_ids = map_ids(actual) + expected_ids = map_ids(expected) + + expect(actual_ids).to match_array(expected_ids) + end + + description do + 'matches elements by ids' + end + + def map_ids(elements) + elements = elements.flatten if elements.respond_to?(:flatten) + + if elements.respond_to?(:map) + elements.map(&:id) + elsif elements.respond_to?(:id) + [elements.id] + else + raise ArgumentError, "could not map elements to ids: #{elements}" + end + end +end diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb new file mode 100644 index 00000000000..87d12a784ba --- /dev/null +++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb @@ -0,0 +1,70 @@ +shared_examples 'cluster application core specs' do |application_name| + it { is_expected.to belong_to(:cluster) } + it { is_expected.to validate_presence_of(:cluster) } + + describe '#name' do + it 'is .application_name' do + expect(subject.name).to eq(described_class.application_name) + end + + it 'is recorded in Clusters::Cluster::APPLICATIONS' do + expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class) + end + end + + describe 'status state machine' do + describe '#make_installing' do + subject { create(application_name, :scheduled) } + + it 'is installing' do + subject.make_installing! + + expect(subject).to be_installing + end + end + + describe '#make_installed' do + subject { create(application_name, :installing) } + + it 'is installed' do + subject.make_installed + + expect(subject).to be_installed + end + end + + describe '#make_errored' do + subject { create(application_name, :installing) } + let(:reason) { 'some errors' } + + it 'is errored' do + subject.make_errored(reason) + + expect(subject).to be_errored + expect(subject.status_reason).to eq(reason) + end + end + + describe '#make_scheduled' do + subject { create(application_name, :installable) } + + it 'is scheduled' do + subject.make_scheduled + + expect(subject).to be_scheduled + end + + describe 'when was errored' do + subject { create(application_name, :errored) } + + it 'clears #status_reason' do + expect(subject.status_reason).not_to be_nil + + subject.make_scheduled! + + expect(subject.status_reason).to be_nil + end + end + end + end +end diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb new file mode 100644 index 00000000000..765dd32f4ba --- /dev/null +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -0,0 +1,31 @@ +shared_examples 'cluster application status specs' do |application_name| + describe '#status' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + subject { described_class.new(cluster: cluster) } + + it 'sets a default status' do + expect(subject.status_name).to be(:not_installable) + end + + context 'when application helm is scheduled' do + before do + create(:clusters_applications_helm, :scheduled, cluster: cluster) + end + + it 'defaults to :not_installable' do + expect(subject.status_name).to be(:not_installable) + end + end + + context 'when application is scheduled' do + before do + create(:clusters_applications_helm, :installed, cluster: cluster) + end + + it 'sets a default status' do + expect(subject.status_name).to be(:installable) + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb new file mode 100644 index 00000000000..b6aeb30d69c --- /dev/null +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -0,0 +1,169 @@ +shared_examples 'discussions API' do |parent_type, noteable_type, id_name| + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "returns an array of discussions" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(note.discussion_id) + end + + it "returns a 404 error when noteable id not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/12345/discussions", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do + it "returns a discussion by id" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(note.discussion_id) + expect(json_response['notes'].first['body']).to eq(note.note) + end + + it "returns a 404 error if discussion not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "creates a new note" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['notes'].first['body']).to eq('hi!') + expect(json_response['notes'].first['author']['username']).to eq(user.username) + end + + it "returns a 400 bad request error if body not given" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 401 unauthorized error if user not authenticated" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), body: 'hi!' + + expect(response).to have_gitlab_http_status(401) + end + + context 'when an admin or owner makes the request' do + it 'accepts the creation date to be set' do + creation_time = 2.weeks.ago + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), + body: 'hi!', created_at: creation_time + + expect(response).to have_gitlab_http_status(201) + expect(json_response['notes'].first['body']).to eq('hi!') + expect(json_response['notes'].first['author']['username']).to eq(user.username) + expect(Time.parse(json_response['notes'].first['created_at'])).to be_like_time(creation_time) + end + end + + context 'when user does not have access to read the discussion' do + before do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'responds with 404' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user), + body: 'Foo' + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do + it 'adds a new note to the discussion' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user), body: 'Hello!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('Hello!') + expect(json_response['type']).to eq('DiscussionNote') + end + + it 'returns a 400 bad request error if body not given' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 400 bad request error if discussion is individual note" do + note.update_attribute(:type, nil) + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + it 'returns modified note' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user), body: 'Hello!' + + expect(response).to have_gitlab_http_status(200) + expect(json_response['body']).to eq('Hello!') + end + + it 'returns a 404 error when note id not found' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/12345", user), + body: 'Hello!' + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 400 bad request error if body not given' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + it 'deletes a note' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(204) + # Check if note is really deleted + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + + it_behaves_like '412 response' do + let(:request) do + api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb new file mode 100644 index 00000000000..79b2196660c --- /dev/null +++ b/spec/support/shared_examples/requests/api/notes.rb @@ -0,0 +1,206 @@ +shared_examples 'noteable API' do |parent_type, noteable_type, id_name| + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + context 'sorting' do + before do + params = { noteable: noteable, author: user } + params[:project] = parent if parent.is_a?(Project) + + create_list(:note, 3, params) + end + + it 'sorts by created_at in descending order by default' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + + response_dates = json_response.map { |note| note['created_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by ascending order when requested' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?sort=asc", user) + + response_dates = json_response.map { |note| note['created_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at in descending order when requested' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at", user) + + response_dates = json_response.map { |note| note['updated_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at in ascending order when requested' do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at&sort=asc", user) + + response_dates = json_response.map { |note| note['updated_at'] } + + expect(json_response.length).to eq(4) + expect(response_dates).to eq(response_dates.sort) + end + end + + it "returns an array of notes" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['body']).to eq(note.note) + end + + it "returns a 404 error when noteable id not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/12345/notes", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + it "returns a note by id" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['body']).to eq(note.note) + end + + it "returns a 404 error if note not found" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do + it "creates a new note" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) + end + + it "returns a 400 bad request error if body not given" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 401 unauthorized error if user not authenticated" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), body: 'hi!' + + expect(response).to have_gitlab_http_status(401) + end + + it "creates an activity event when a note is created" do + expect(Event).to receive(:create!) + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!' + end + + context 'when an admin or owner makes the request' do + it 'accepts the creation date to be set' do + creation_time = 2.weeks.ago + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), + body: 'hi!', created_at: creation_time + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + end + end + + context 'when the user is posting an award emoji on a noteable created by someone else' do + it 'creates a new note' do + parent.add_developer(private_user) + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), body: ':+1:' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq(':+1:') + end + end + + context 'when the user is posting an award emoji on his/her own noteable' do + it 'creates a new note' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: ':+1:' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq(':+1:') + end + end + + context 'when user does not have access to read the noteable' do + before do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'responds with 404' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), + body: 'Foo' + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + it 'returns modified note' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user), body: 'Hello!' + + expect(response).to have_gitlab_http_status(200) + expect(json_response['body']).to eq('Hello!') + end + + it 'returns a 404 error when note id not found' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user), + body: 'Hello!' + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 400 bad request error if body not given' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "DELETE /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do + it 'deletes a note' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(204) + # Check if note is really deleted + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "notes/#{note.id}", user) + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 404 error when note id not found' do + delete api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + + it_behaves_like '412 response' do + let(:request) { api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/#{note.id}", user) } + end + end +end diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb new file mode 100644 index 00000000000..5bdc04f660f --- /dev/null +++ b/spec/support/shared_examples/services/boards/boards_create_service.rb @@ -0,0 +1,27 @@ +shared_examples 'boards create service' do + context 'when parent does not have a board' do + it 'creates a new board' do + expect { service.execute }.to change(Board, :count).by(1) + end + + it 'creates the default lists' do + board = service.execute + + expect(board.lists.size).to eq 2 + expect(board.lists.first).to be_backlog + expect(board.lists.last).to be_closed + end + end + + context 'when parent has a board' do + before do + create(:board, parent: parent) + end + + it 'does not create a new board' do + expect(service).to receive(:can_create_board?) { false } + + expect { service.execute }.not_to change(parent.boards, :count) + end + end +end diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb new file mode 100644 index 00000000000..e0d5a7c61f2 --- /dev/null +++ b/spec/support/shared_examples/services/boards/boards_list_service.rb @@ -0,0 +1,29 @@ +shared_examples 'boards list service' do + context 'when parent does not have a board' do + it 'creates a new parent board' do + expect { service.execute }.to change(parent.boards, :count).by(1) + end + + it 'delegates the parent board creation to Boards::CreateService' do + expect_any_instance_of(Boards::CreateService).to receive(:execute).once + + service.execute + end + end + + context 'when parent has a board' do + before do + create(:board, parent: parent) + end + + it 'does not create a new board' do + expect { service.execute }.not_to change(parent.boards, :count) + end + end + + it 'returns parent boards' do + board = create(:board, parent: parent) + + expect(service.execute).to eq [board] + end +end diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb new file mode 100644 index 00000000000..3e744323cea --- /dev/null +++ b/spec/support/shared_examples/services/boards/issues_list_service.rb @@ -0,0 +1,60 @@ +shared_examples 'issues list service' do + it 'delegates search to IssuesFinder' do + params = { board_id: board.id, id: list1.id } + + expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original + + described_class.new(parent, user, params).execute + end + + context 'issues are ordered by priority' do + it 'returns opened issues when list_id is missing' do + params = { board_id: board.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] + end + + it 'returns opened issues when listing issues from Backlog' do + params = { board_id: board.id, id: backlog.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] + end + + it 'returns closed issues when listing issues from Closed' do + params = { board_id: board.id, id: closed.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1] + end + + it 'returns opened issues that have label list applied when listing issues from a label list' do + params = { board_id: board.id, id: list1.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] + end + end + + context 'with list that does not belong to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(parent, user, board_id: board.id, id: list.id) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(parent, user, board_id: board.id, id: nil) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb new file mode 100644 index 00000000000..4a4fbaa3a0e --- /dev/null +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -0,0 +1,87 @@ +shared_examples 'issues move service' do + context 'when moving an issue between lists' do + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } + + it 'delegates the label changes to Issues::UpdateService' do + expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once + + described_class.new(parent, user, params).execute(issue) + end + + it 'removes the label from the list it came from and adds the label of the list it goes to' do + described_class.new(parent, user, params).execute(issue) + + expect(issue.reload.labels).to contain_exactly(bug, testing) + end + end + + context 'when moving to closed' do + let!(:list3) { create(:list, board: board2, label: regression, position: 1) } + + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } + + it 'delegates the close proceedings to Issues::CloseService' do + expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once + + described_class.new(parent, user, params).execute(issue) + end + + it 'removes all list-labels from boards and close the issue' do + described_class.new(parent, user, params).execute(issue) + issue.reload + + expect(issue.labels).to contain_exactly(bug) + expect(issue).to be_closed + end + end + + context 'when moving from closed' do + let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } + let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } + + it 'delegates the re-open proceedings to Issues::ReopenService' do + expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once + + described_class.new(parent, user, params).execute(issue) + end + + it 'adds the label of the list it goes to and reopen the issue' do + described_class.new(parent, user, params).execute(issue) + issue.reload + + expect(issue.labels).to contain_exactly(bug, testing) + expect(issue).to be_opened + end + end + + context 'when moving to same list' do + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + + it 'returns false' do + expect(described_class.new(parent, user, params).execute(issue)).to eq false + end + + it 'keeps issues labels' do + described_class.new(parent, user, params).execute(issue) + + expect(issue.reload.labels).to contain_exactly(bug, development) + end + + it 'sorts issues' do + [issue, issue1, issue2].each do |issue| + issue.move_to_end && issue.save! + end + + params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) + + described_class.new(parent, user, params).execute(issue) + + expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) + end + end +end diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb new file mode 100644 index 00000000000..62b6ffe1836 --- /dev/null +++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb @@ -0,0 +1,30 @@ +shared_examples 'lists destroy service' do + context 'when list type is label' do + it 'removes list from board' do + list = create(:list, board: board) + service = described_class.new(parent, user) + + expect { service.execute(list) }.to change(board.lists, :count).by(-1) + end + + it 'decrements position of higher lists' do + development = create(:list, board: board, position: 0) + review = create(:list, board: board, position: 1) + staging = create(:list, board: board, position: 2) + closed = board.closed_list + + described_class.new(parent, user).execute(development) + + expect(review.reload.position).to eq 0 + expect(staging.reload.position).to eq 1 + expect(closed.reload.position).to be_nil + end + end + + it 'does not remove list from board when list type is closed' do + list = board.closed_list + service = described_class.new(parent, user) + + expect { service.execute(list) }.not_to change(board.lists, :count) + end +end diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb new file mode 100644 index 00000000000..0a8220111ab --- /dev/null +++ b/spec/support/shared_examples/services/boards/lists_list_service.rb @@ -0,0 +1,23 @@ +shared_examples 'lists list service' do + context 'when the board has a backlog list' do + let!(:backlog_list) { create(:backlog_list, board: board) } + + it 'does not create a backlog list' do + expect { service.execute(board) }.not_to change(board.lists, :count) + end + + it "returns board's lists" do + expect(service.execute(board)).to eq [backlog_list, list, board.closed_list] + end + end + + context 'when the board does not have a backlog list' do + it 'creates a backlog list' do + expect { service.execute(board) }.to change(board.lists, :count).by(1) + end + + it "returns board's lists" do + expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list] + end + end +end diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb new file mode 100644 index 00000000000..07c98cb29b7 --- /dev/null +++ b/spec/support/shared_examples/services/boards/lists_move_service.rb @@ -0,0 +1,93 @@ +shared_examples 'lists move service' do + let!(:planning) { create(:list, board: board, position: 0) } + let!(:development) { create(:list, board: board, position: 1) } + let!(:review) { create(:list, board: board, position: 2) } + let!(:staging) { create(:list, board: board, position: 3) } + let!(:closed) { create(:closed_list, board: board) } + + context 'when list type is set to label' do + it 'keeps position of lists when new position is nil' do + service = described_class.new(parent, user, position: nil) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is equal to old position' do + service = described_class.new(parent, user, position: planning.position) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is negative' do + service = described_class.new(parent, user, position: -1) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is equal to number of labels lists' do + service = described_class.new(parent, user, position: board.lists.label.size) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is greater than number of labels lists' do + service = described_class.new(parent, user, position: board.lists.label.size + 1) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'increments position of intermediate lists when new positon is equal to first position' do + service = described_class.new(parent, user, position: 0) + + service.execute(staging) + + expect(current_list_positions).to eq [1, 2, 3, 0] + end + + it 'decrements position of intermediate lists when new positon is equal to last position' do + service = described_class.new(parent, user, position: board.lists.label.last.position) + + service.execute(planning) + + expect(current_list_positions).to eq [3, 0, 1, 2] + end + + it 'decrements position of intermediate lists when new position is greater than old position' do + service = described_class.new(parent, user, position: 2) + + service.execute(planning) + + expect(current_list_positions).to eq [2, 0, 1, 3] + end + + it 'increments position of intermediate lists when new position is lower than old position' do + service = described_class.new(parent, user, position: 1) + + service.execute(staging) + + expect(current_list_positions).to eq [0, 2, 3, 1] + end + end + + it 'keeps position of lists when list type is closed' do + service = described_class.new(parent, user, position: 2) + + service.execute(closed) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + def current_list_positions + [planning, development, review, staging].map { |list| list.reload.position } + end +end diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index e827a8da0b7..5e1ce19eafb 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -337,6 +337,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do before do chat_service.notify_only_default_branch = true + WebMock.stub_request(:post, webhook_url) end it 'does not call the Slack/Mattermost API for pipeline events' do @@ -345,6 +346,23 @@ RSpec.shared_examples 'slack or mattermost notifications' do expect(result).to be_falsy end + + it 'does not notify push events if they are not for the default branch' do + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" + push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + + chat_service.execute(push_sample_data) + + expect(WebMock).not_to have_requested(:post, webhook_url) + end + + it 'notifies about push events for the default branch' do + push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) + + chat_service.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end end context 'when disabled' do |