diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-14 12:06:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-14 12:06:30 +0000 |
commit | d8c06be498acbfc2024c01b6b6b02d120dc499f2 (patch) | |
tree | 9e2e0852c45332d6222898676a2f6f096e600084 /spec | |
parent | 2fa7d2ddf6a7004f89616e43b8279229af831e25 (diff) | |
download | gitlab-ce-d8c06be498acbfc2024c01b6b6b02d120dc499f2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
18 files changed, 485 insertions, 69 deletions
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb index 15f8fa7438d..9d38c44b6ef 100644 --- a/spec/features/projects/files/user_reads_pipeline_status_spec.rb +++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb @@ -9,8 +9,6 @@ describe 'user reads pipeline status', :js do let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') } before do - stub_feature_flags(vue_file_list: false) - project.add_maintainer(user) project.repository.add_tag(user, 'x1.1.0', 'v1.1.0') @@ -25,7 +23,7 @@ describe 'user reads pipeline status', :js do visit project_tree_path(project, expected_pipeline.ref) wait_for_requests - page.within('.blob-commit-info') do + page.within('.commit-detail') do expect(page).to have_link('', href: project_pipeline_path(project, expected_pipeline)) expect(page).to have_selector(".ci-status-icon-#{expected_pipeline.status}") end diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js index ff079082ca7..a7a1d563e1e 100644 --- a/spec/frontend/cycle_analytics/stage_nav_item_spec.js +++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js @@ -133,45 +133,19 @@ describe('StageNavItem', () => { hasStageName(); }); - it('renders options menu', () => { - expect(wrapper.find('.more-actions-toggle').exists()).toBe(true); + it('does not render options menu', () => { + expect(wrapper.find('.more-actions-toggle').exists()).toBe(false); }); - describe('Default stages', () => { - beforeEach(() => { - wrapper = createComponent( - { canEdit: true, isUserAllowed: true, isDefaultStage: true }, - false, - ); - }); - it('can hide the stage', () => { - expect(wrapper.text()).toContain('Hide stage'); - }); - it('can not edit the stage', () => { - expect(wrapper.text()).not.toContain('Edit stage'); - }); - it('can not remove the stage', () => { - expect(wrapper.text()).not.toContain('Remove stage'); - }); + it('can not edit the stage', () => { + expect(wrapper.text()).not.toContain('Edit stage'); + }); + it('can not remove the stage', () => { + expect(wrapper.text()).not.toContain('Remove stage'); }); - describe('Custom stages', () => { - beforeEach(() => { - wrapper = createComponent( - { canEdit: true, isUserAllowed: true, isDefaultStage: false }, - false, - ); - }); - it('can edit the stage', () => { - expect(wrapper.text()).toContain('Edit stage'); - }); - it('can remove the stage', () => { - expect(wrapper.text()).toContain('Remove stage'); - }); - - it('can not hide the stage', () => { - expect(wrapper.text()).not.toContain('Hide stage'); - }); + it('can not hide the stage', () => { + expect(wrapper.text()).not.toContain('Hide stage'); }); }); }); diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js index 01b56d453e6..e07ad4cf46b 100644 --- a/spec/frontend/repository/components/last_commit_spec.js +++ b/spec/frontend/repository/components/last_commit_spec.js @@ -17,7 +17,7 @@ function createCommitData(data = {}) { avatarUrl: 'https://test.com', webUrl: 'https://test.com/test', }, - latestPipeline: { + pipeline: { detailedStatus: { detailsPath: 'https://test.com/pipeline', icon: 'failed', @@ -74,7 +74,7 @@ describe('Repository last commit component', () => { }); it('hides pipeline components when pipeline does not exist', () => { - factory(createCommitData({ latestPipeline: null })); + factory(createCommitData({ pipeline: null })); expect(vm.find('.js-commit-pipeline').exists()).toBe(false); }); diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap index 3d5ec3fd411..a5e3eb4bce1 100644 --- a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap +++ b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap @@ -2,7 +2,7 @@ exports[`Repository file preview component renders file HTML 1`] = ` <article - class="file-holder js-hide-on-navigation limited-width-container readme-holder" + class="file-holder limited-width-container readme-holder" > <div class="file-title" diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js index 954c4791c04..148e307a5d4 100644 --- a/spec/frontend/repository/components/tree_content_spec.js +++ b/spec/frontend/repository/components/tree_content_spec.js @@ -28,7 +28,7 @@ describe('Repository table component', () => { it('renders file preview', () => { factory('/'); - vm.setData({ entries: { blobs: [{ name: 'README.md ' }] } }); + vm.setData({ entries: { blobs: [{ name: 'README.md' }] } }); expect(vm.find(FilePreview).exists()).toBe(true); }); diff --git a/spec/frontend/repository/utils/readme_spec.js b/spec/frontend/repository/utils/readme_spec.js new file mode 100644 index 00000000000..6b7876c8947 --- /dev/null +++ b/spec/frontend/repository/utils/readme_spec.js @@ -0,0 +1,33 @@ +import { readmeFile } from '~/repository/utils/readme'; + +describe('readmeFile', () => { + describe('markdown files', () => { + it('returns markdown file', () => { + expect(readmeFile([{ name: 'README' }, { name: 'README.md' }])).toEqual({ + name: 'README.md', + }); + + expect(readmeFile([{ name: 'README' }, { name: 'index.md' }])).toEqual({ + name: 'index.md', + }); + }); + }); + + describe('plain files', () => { + it('returns plain file', () => { + expect(readmeFile([{ name: 'README' }, { name: 'TEST.md' }])).toEqual({ + name: 'README', + }); + + expect(readmeFile([{ name: 'readme' }, { name: 'TEST.md' }])).toEqual({ + name: 'readme', + }); + }); + }); + + describe('non-previewable file', () => { + it('returns undefined', () => { + expect(readmeFile([{ name: 'index.js' }, { name: 'TEST.md' }])).toBe(undefined); + }); + }); +}); diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index c162fdbbb47..a212bd07f35 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -13,6 +13,14 @@ describe Resolvers::BaseResolver do end end + let(:last_resolver) do + Class.new(described_class) do + def resolve(**args) + [1, 2] + end + end + end + describe '.single' do it 'returns a subclass from the resolver' do expect(resolver.single.superclass).to eq(resolver) @@ -29,6 +37,22 @@ describe Resolvers::BaseResolver do end end + describe '.last' do + it 'returns a subclass from the resolver' do + expect(last_resolver.last.superclass).to eq(last_resolver) + end + + it 'returns the same subclass every time' do + expect(last_resolver.last.object_id).to eq(last_resolver.last.object_id) + end + + it 'returns a resolver that gives the last result from the original resolver' do + result = resolve(last_resolver.last) + + expect(result).to eq(2) + end + end + context 'when field is a connection' do it 'increases complexity based on arguments' do field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1) diff --git a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb new file mode 100644 index 00000000000..93da877d714 --- /dev/null +++ b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::CommitPipelinesResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let(:commit) { create(:commit, project: project) } + let_it_be(:current_user) { create(:user) } + + let!(:pipeline) do + create( + :ci_pipeline, + project: project, + sha: commit.id, + ref: 'master', + status: 'success' + ) + end + let!(:pipeline2) do + create( + :ci_pipeline, + project: project, + sha: commit.id, + ref: 'master', + status: 'failed' + ) + end + let!(:pipeline3) do + create( + :ci_pipeline, + project: project, + sha: commit.id, + ref: 'my_branch', + status: 'failed' + ) + end + + before do + commit.project.add_developer(current_user) + end + + def resolve_pipelines + resolve(described_class, obj: commit, ctx: { current_user: current_user }, args: { ref: 'master' }) + end + + it 'resolves pipelines for commit and ref' do + pipelines = resolve_pipelines + + expect(pipelines).to eq([pipeline2, pipeline]) + end +end diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb index 1ff1c97f8db..ee9af886e60 100644 --- a/spec/graphql/types/commit_type_spec.rb +++ b/spec/graphql/types/commit_type_spec.rb @@ -10,7 +10,7 @@ describe GitlabSchema.types['Commit'] do it 'contains attributes related to commit' do expect(described_class).to have_graphql_fields( :id, :sha, :title, :description, :message, :authored_date, - :author, :web_url, :latest_pipeline, :signature_html + :author, :web_url, :latest_pipeline, :pipelines, :signature_html ) end end diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index b2fe315f6c6..b53e30b6896 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import AjaxFormVariableList from '~/ci_variable_list/ajax_variable_list'; -const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/variables'; +const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/-/variables'; const HIDE_CLASS = 'hide'; describe('AjaxFormVariableList', () => { diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb index dcd1a2d9813..676511211c8 100644 --- a/spec/lib/gitlab/fogbugz_import/client_spec.rb +++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::FogbugzImport::Client do diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb deleted file mode 100644 index 136027736c3..00000000000 --- a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Graphql::Loaders::PipelineForShaLoader do - include GraphqlHelpers - - describe '#find_last' do - it 'batch-resolves latest pipeline' do - project = create(:project, :repository) - pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) - pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha) - pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha) - - result = batch_sync(max_queries: 1) do - [pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last } - end - - expect(result).to contain_exactly(pipeline2, pipeline3) - end - end -end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index b86663fd7d9..0f7f68e0b38 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -378,6 +378,14 @@ describe MergeRequestDiff do expect(diff_with_commits.commit_shas).not_to be_empty expect(diff_with_commits.commit_shas).to all(match(/\h{40}/)) end + + context 'with limit attribute' do + it 'returns limited number of shas' do + expect(diff_with_commits.commit_shas(limit: 2).size).to eq(2) + expect(diff_with_commits.commit_shas(limit: 100).size).to eq(29) + expect(diff_with_commits.commit_shas.size).to eq(29) + end + end end describe '#compare_with' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index f775dfb87a2..b19f7a80d63 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1261,13 +1261,49 @@ describe MergeRequest do end describe '#commit_shas' do - before do - allow(subject.merge_request_diff).to receive(:commit_shas) - .and_return(['sha1']) + context 'persisted merge request' do + context 'with a limit' do + it 'returns a limited number of commit shas' do + expect(subject.commit_shas(limit: 2)).to eq(%w[ + b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6 + ]) + end + end + + context 'without a limit' do + it 'returns all commit shas of the merge request diff' do + expect(subject.commit_shas.size).to eq(29) + end + end end - it 'delegates to merge request diff' do - expect(subject.commit_shas).to eq ['sha1'] + context 'new merge request' do + subject { build(:merge_request) } + + context 'compare commits' do + before do + subject.compare_commits = [ + double(sha: 'sha1'), double(sha: 'sha2') + ] + end + + context 'without a limit' do + it 'returns all shas of compare commits' do + expect(subject.commit_shas).to eq(%w[sha2 sha1]) + end + end + + context 'with a limit' do + it 'returns a limited number of shas' do + expect(subject.commit_shas(limit: 1)).to eq(['sha2']) + end + end + end + + it 'returns diff_head_sha as an array' do + expect(subject.commit_shas).to eq([subject.diff_head_sha]) + expect(subject.commit_shas(limit: 2)).to eq([subject.diff_head_sha]) + end end end diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb new file mode 100644 index 00000000000..ac4853e5388 --- /dev/null +++ b/spec/requests/api/group_export_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::GroupExport do + let_it_be(:group) { create(:group) } + let_it_be(:user) { create(:user) } + + let(:path) { "/groups/#{group.id}/export" } + let(:download_path) { "/groups/#{group.id}/export/download" } + + let(:export_path) { "#{Dir.tmpdir}/group_export_spec" } + + before do + allow_next_instance_of(Gitlab::ImportExport) do |import_export| + expect(import_export).to receive(:storage_path).and_return(export_path) + end + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + describe 'GET /groups/:group_id/export/download' do + let(:upload) { ImportExportUpload.new(group: group) } + + before do + stub_uploads_object_storage(ImportExportUploader) + + group.add_owner(user) + end + + context 'when export file exists' do + before do + upload.export_file = fixture_file_upload('spec/fixtures/group_export.tar.gz', "`/tar.gz") + upload.save! + end + + it 'downloads exported group archive' do + get api(download_path, user) + + expect(response).to have_gitlab_http_status(200) + end + + context 'when export_file.file does not exist' do + before do + expect_next_instance_of(ImportExportUploader) do |uploader| + expect(uploader).to receive(:file).and_return(nil) + end + end + + it 'returns 404' do + get api(download_path, user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + context 'when export file does not exist' do + it 'returns 404' do + get api(download_path, user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe 'POST /groups/:group_id/export' do + context 'when user is a group owner' do + before do + group.add_owner(user) + end + + it 'accepts download' do + post api(path, user) + + expect(response).to have_gitlab_http_status(202) + end + end + + context 'when user is not a group owner' do + before do + group.add_developer(user) + end + + it 'forbids the request' do + post api(path, user) + + expect(response).to have_gitlab_http_status(403) + end + end + end +end diff --git a/spec/support/helpers/access_matchers_helpers.rb b/spec/support/helpers/access_matchers_helpers.rb new file mode 100644 index 00000000000..9100f245d36 --- /dev/null +++ b/spec/support/helpers/access_matchers_helpers.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module AccessMatchersHelpers + USER_ACCESSOR_METHOD_NAME = 'user' + + def provide_user(role, membership = nil) + case role + when :admin + create(:admin) + when :auditor + create(:user, :auditor) + when :user + create(:user) + when :external + create(:user, :external) + when :visitor, :anonymous + nil + when User + role + when *Gitlab::Access.sym_options_with_owner.keys # owner, maintainer, developer, reporter, guest + raise ArgumentError, "cannot provide #{role} when membership reference is blank" unless membership + + provide_user_by_membership(role, membership) + else + raise ArgumentError, "cannot provide user of an unknown role #{role}" + end + end + + def provide_user_by_membership(role, membership) + if role == :owner && membership.owner + membership.owner + else + create(:user).tap do |user| + membership.public_send(:"add_#{role}", user) + end + end + end + + def raise_if_non_block_expectation!(actual) + raise ArgumentError, 'This matcher supports block expectations only.' unless actual.is_a?(Proc) + end + + def update_owner(objects, user) + return unless objects + + objects.each do |object| + if object.respond_to?(:owner) + object.update_attribute(:owner, user) + elsif object.respond_to?(:user) + object.update_attribute(:user, user) + else + raise ArgumentError, "cannot own this object #{object}" + end + end + end + + def patch_example_group(user) + return if user.nil? # for anonymous users + + # This call is evaluated in context of ExampleGroup instance in which the matcher is called. Overrides the `user` + # (or defined by `method_name`) method generated by `let` definition in example group before it's used by `subject`. + # This override is per concrete example only because the example group class gets re-created for each example. + instance_eval(<<~CODE, __FILE__, __LINE__ + 1) + if instance_variable_get(:@__#{USER_ACCESSOR_METHOD_NAME}_patched) + raise ArgumentError, 'An access matcher be_allowed_for/be_denied_for can be used only once per example (`it` block)' + end + instance_variable_set(:@__#{USER_ACCESSOR_METHOD_NAME}_patched, true) + + def #{USER_ACCESSOR_METHOD_NAME} + @#{USER_ACCESSOR_METHOD_NAME} ||= User.find(#{user.id}) + end + CODE + end + + def prepare_matcher_environment(role, membership, owned_objects) + user = provide_user(role, membership) + + if user + update_owner(owned_objects, user) + patch_example_group(user) + end + end + + def run_matcher(action, role, membership, owned_objects) + raise_if_non_block_expectation!(action) + + prepare_matcher_environment(role, membership, owned_objects) + + if block_given? + yield action + else + action.call + end + end +end diff --git a/spec/support/matchers/access_matchers_for_request.rb b/spec/support/matchers/access_matchers_for_request.rb new file mode 100644 index 00000000000..9b80bf8562c --- /dev/null +++ b/spec/support/matchers/access_matchers_for_request.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# AccessMatchersForRequest +# +# Matchers to test the access permissions for requests specs (most useful for API tests). +module AccessMatchersForRequest + extend RSpec::Matchers::DSL + include AccessMatchersHelpers + + EXPECTED_STATUS_CODES_ALLOWED = [200, 201, 204, 302, 304].freeze + EXPECTED_STATUS_CODES_DENIED = [401, 403, 404].freeze + + def description_for(role, type, expected, result) + "be #{type} for #{role} role. Expected status code: any of #{expected.join(', ')} Got: #{result}" + end + + matcher :be_allowed_for do |role| + match do |action| + # methods called in this and negated block are being run in context of ExampleGroup + # (not matcher) instance so we have to pass data via local vars + + run_matcher(action, role, @membership, @owned_objects) + + EXPECTED_STATUS_CODES_ALLOWED.include?(response.status) + end + + match_when_negated do |action| + run_matcher(action, role, @membership, @owned_objects) + + EXPECTED_STATUS_CODES_DENIED.include?(response.status) + end + + chain :of do |membership| + @membership = membership + end + + chain :own do |*owned_objects| + @owned_objects = owned_objects + end + + failure_message do + "expected this action to #{description_for(role, 'allowed', EXPECTED_STATUS_CODES_ALLOWED, response.status)}" + end + + failure_message_when_negated do + "expected this action to #{description_for(role, 'denied', EXPECTED_STATUS_CODES_DENIED, response.status)}" + end + + supports_block_expectations + end + + RSpec::Matchers.define_negated_matcher :be_denied_for, :be_allowed_for +end diff --git a/spec/support/matchers/access_matchers_generic.rb b/spec/support/matchers/access_matchers_generic.rb new file mode 100644 index 00000000000..13955750f4f --- /dev/null +++ b/spec/support/matchers/access_matchers_generic.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# AccessMatchersGeneric +# +# Matchers to test the access permissions for service classes or other generic pieces of business logic. +module AccessMatchersGeneric + extend RSpec::Matchers::DSL + include AccessMatchersHelpers + + ERROR_CLASS = Gitlab::Access::AccessDeniedError + + def error_message(error) + str = error.class.name + str += ": #{error.message}" if error.message != error.class.name + str + end + + def error_expectation_message(allowed, error) + if allowed + "Expected to raise nothing but #{error_message(error)} was raised." + else + "Expected to raise #{ERROR_CLASS} but nothing was raised." + end + end + + def description_for(role, type, error) + allowed = type == 'allowed' + "be #{type} for #{role} role. #{error_expectation_message(allowed, error)}" + end + + matcher :be_allowed_for do |role| + match do |action| + # methods called in this and negated block are being run in context of ExampleGroup + # (not matcher) instance so we have to pass data via local vars + + run_matcher(action, role, @membership, @owned_objects) do |action| + action.call + rescue => e + @error = e + raise unless e.is_a?(ERROR_CLASS) + end + + @error.nil? + end + + chain :of do |membership| + @membership = membership + end + + chain :own do |*owned_objects| + @owned_objects = owned_objects + end + + failure_message do + "expected this action to #{description_for(role, 'allowed', @error)}" + end + + failure_message_when_negated do + "expected this action to #{description_for(role, 'denied', @error)}" + end + + supports_block_expectations + end + + RSpec::Matchers.define_negated_matcher :be_denied_for, :be_allowed_for +end |