From 0f5b735685476ff51b26031e9b74fa8256354868 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 3 Apr 2019 22:00:29 -0700 Subject: Fix stage index migration failing in PostgreSQL 10 As discussed in https://www.postgresql.org/message-id/9922.1353433645%40sss.pgh.pa.us, the PostgreSQL window function last_value may not consider the right rows: Note that first_value, last_value, and nth_value consider only the rows within the "window frame", which by default contains the rows from the start of the partition through the last peer of the current row. This is likely to give unhelpful results for last_value and sometimes also nth_value. You can redefine the frame by adding a suitable frame specification (RANGE or ROWS) to the OVER clause. See Section 4.2.8 for more information about frame specifications. This query could be fixed by adding `RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING`, but that's quite verbose. It's simpler just to use the first_value function. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/59985 --- spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb index f8107dd40b9..4db829b1e7b 100644 --- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb @@ -30,6 +30,6 @@ describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 201 described_class.new.perform(100, 101) - expect(stages.all.pluck(:position)).to eq [2, 3] + expect(stages.all.order(:id).pluck(:position)).to eq [2, 3] end end -- cgit v1.2.1 From f3ad51f8a57df96bcc69b0821355ef29c3df2ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 8 Apr 2019 14:41:05 +0200 Subject: Improve performance of PR import This removes unneeded `.reload` call which makes AR to load ALL objects, and create its in-memory representation. --- .../gitlab/import/merge_request_helpers_spec.rb | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 spec/lib/gitlab/import/merge_request_helpers_spec.rb (limited to 'spec/lib') diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb new file mode 100644 index 00000000000..cc0f2baf905 --- /dev/null +++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Import::MergeRequestHelpers, type: :helper do + set(:project) { create(:project, :repository) } + set(:user) { create(:user) } + + describe '.create_merge_request_without_hooks' do + let(:iid) { 42 } + + let(:attributes) do + { + iid: iid, + title: 'My Pull Request', + description: 'This is my pull request', + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'master-42', + target_branch: 'master', + state: :merged, + author_id: user.id, + assignee_id: user.id + } + end + + subject { helper.create_merge_request_without_hooks(project, attributes, iid) } + + context 'when merge request does not exist' do + it 'returns a new object' do + expect(subject.first).not_to be_nil + expect(subject.second).to eq(false) + end + + it 'does load all existing objects' do + 5.times do |iid| + MergeRequest.create!( + attributes.merge(iid: iid, source_branch: iid.to_s)) + end + + # does ensure that we only load object twice + # 1. by #insert_and_return_id + # 2. by project.merge_requests.find + expect_any_instance_of(MergeRequest).to receive(:attributes) + .twice.times.and_call_original + + expect(subject.first).not_to be_nil + expect(subject.second).to eq(false) + end + end + + context 'when merge request does exist' do + before do + MergeRequest.create!(attributes) + end + + it 'returns an existing object' do + expect(subject.first).not_to be_nil + expect(subject.second).to eq(true) + end + end + + context 'when project is deleted' do + before do + project.delete + end + + it 'returns an existing object' do + expect(subject.first).to be_nil + end + end + end +end -- cgit v1.2.1 From 26fdcf7b6103aa47943271a5f6358d9779d5a9b3 Mon Sep 17 00:00:00 2001 From: Daniel Wyatt Date: Mon, 8 Apr 2019 10:12:39 -0400 Subject: Fix GitHub project import visibility --- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 3d4240fa4ba..8c56622e0ba 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -47,12 +47,12 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do end context 'when GitHub project is public' do - it 'sets project visibility to public' do + it 'sets project visibility to namespace visibility level' do repo.private = false project = service.execute - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + expect(project.visibility_level).to eq(namespace.visibility_level) end end -- cgit v1.2.1 From aa352a95df665ded5178c1b26d4492433e47714e Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Fri, 29 Mar 2019 14:07:03 +1300 Subject: Support merge request create with push options To create a new merge request: git push -u origin -o merge_request.create To create a new merge request setting target branch: git push -u origin -o merge_request.create \ -o merge_request.target=123 To update an existing merge request with a new target branch: git push -u origin -o merge_request.target=123 A new Gitlab::PushOptions class handles parsing and validating the push options array. This can be the start of the standard of GitLab accepting push options that follow namespacing rules. Rules are discussed in issue https://gitlab.com/gitlab-org/gitlab-ce/issues/43263. E.g. these push options: -o merge_request.create -o merge_request.target=123 Become parsed as: { merge_request: { create: true, target: '123', } } And are fetched with the class via: push_options.get(:merge_request) push_options.get(:merge_request, :create) push_options.get(:merge_request, :target) A new MergeRequests::PushOptionsHandlerService takes the `merge_request` namespaced push options and handles creating and updating merge requests. Any errors encountered are passed to the existing `output` Hash in Api::Internal's `post_receive` endpoint, and passed to gitlab-shell where they're output to the user. Issue https://gitlab.com/gitlab-org/gitlab-ce/issues/43263 --- spec/lib/gitlab/push_options_spec.rb | 91 ++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 spec/lib/gitlab/push_options_spec.rb (limited to 'spec/lib') diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb new file mode 100644 index 00000000000..68b64863c21 --- /dev/null +++ b/spec/lib/gitlab/push_options_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::PushOptions do + describe 'namespace and key validation' do + it 'ignores unrecognised namespaces' do + options = described_class.new(['invalid.key=value']) + + expect(options.get(:invalid)).to eq(nil) + end + + it 'ignores unrecognised keys' do + options = described_class.new(['merge_request.key=value']) + + expect(options.get(:merge_request)).to eq(nil) + end + + it 'ignores blank keys' do + options = described_class.new(['merge_request']) + + expect(options.get(:merge_request)).to eq(nil) + end + + it 'parses recognised namespace and key pairs' do + options = described_class.new(['merge_request.target=value']) + + expect(options.get(:merge_request)).to include({ + target: 'value' + }) + end + end + + describe '#get' do + it 'can emulate Hash#dig' do + options = described_class.new(['merge_request.target=value']) + + expect(options.get(:merge_request, :target)).to eq('value') + end + end + + it 'can parse multiple push options' do + options = described_class.new([ + 'merge_request.create', + 'merge_request.target=value' + ]) + + expect(options.get(:merge_request)).to include({ + create: true, + target: 'value' + }) + expect(options.get(:merge_request, :create)).to eq(true) + expect(options.get(:merge_request, :target)).to eq('value') + end + + it 'stores options internally as a HashWithIndifferentAccess' do + options = described_class.new([ + 'merge_request.create' + ]) + + expect(options.get('merge_request', 'create')).to eq(true) + expect(options.get(:merge_request, :create)).to eq(true) + end + + it 'selects the last option when options contain duplicate namespace and key pairs' do + options = described_class.new([ + 'merge_request.target=value1', + 'merge_request.target=value2' + ]) + + expect(options.get(:merge_request, :target)).to eq('value2') + end + + it 'defaults values to true' do + options = described_class.new(['merge_request.create']) + + expect(options.get(:merge_request, :create)).to eq(true) + end + + it 'expands aliases' do + options = described_class.new(['mr.target=value']) + + expect(options.get(:merge_request, :target)).to eq('value') + end + + it 'forgives broken push options' do + options = described_class.new(['merge_request . target = value']) + + expect(options.get(:merge_request, :target)).to eq('value') + end +end -- cgit v1.2.1 From ca884980ee8e6fe1269f5abdb803519d51aa09c0 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Sun, 7 Apr 2019 15:35:16 -0300 Subject: [CE] Support multiple assignees for merge requests Backports https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10161 (code out of ee/ folder). --- spec/lib/gitlab/hook_data/issuable_builder_spec.rb | 6 +++--- spec/lib/gitlab/hook_data/merge_request_builder_spec.rb | 1 + spec/lib/gitlab/import_export/all_models.yml | 4 ++++ spec/lib/gitlab/import_export/safe_model_attributes.yml | 4 ++++ spec/lib/gitlab/issuable_metadata_spec.rb | 4 ++-- 5 files changed, 14 insertions(+), 5 deletions(-) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb index 26529c4759d..569d5dcc757 100644 --- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -97,13 +97,13 @@ describe Gitlab::HookData::IssuableBuilder do end context 'merge_request is assigned' do - let(:merge_request) { create(:merge_request, assignee: user) } + let(:merge_request) { create(:merge_request, assignees: [user]) } let(:data) { described_class.new(merge_request).build(user: user) } it 'returns correct hook data' do expect(data[:object_attributes]['assignee_id']).to eq(user.id) - expect(data[:assignee]).to eq(user.hook_attrs) - expect(data).not_to have_key(:assignees) + expect(data[:assignees].first).to eq(user.hook_attrs) + expect(data).not_to have_key(:assignee) end end end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index 9ce697adbba..39f80f92fa6 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -10,6 +10,7 @@ describe Gitlab::HookData::MergeRequestBuilder do it 'includes safe attribute' do %w[ assignee_id + assignee_ids author_id created_at description diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index e418516569a..ed557ffd4e3 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -102,6 +102,7 @@ merge_requests: - merge_request_pipelines - merge_request_assignees - suggestions +- assignees merge_request_diff: - merge_request - merge_request_diff_commits @@ -336,6 +337,9 @@ push_event_payload: issue_assignees: - issue - assignee +merge_request_assignees: +- merge_request +- assignee lfs_file_locks: - user project_badges: diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index d0ed588f05f..06995604a24 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -621,3 +621,7 @@ Suggestion: - outdated - lines_above - lines_below +MergeRequestAssignee: +- id +- user_id +- merge_request_id diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 6ec86163233..916f3876a8e 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::IssuableMetadata do let!(:closed_issue) { create(:issue, state: :closed, author: user, project: project) } let!(:downvote) { create(:award_emoji, :downvote, awardable: closed_issue) } let!(:upvote) { create(:award_emoji, :upvote, awardable: issue) } - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, title: "Test") } let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) } it 'aggregates stats on issues' do @@ -39,7 +39,7 @@ describe Gitlab::IssuableMetadata do end context 'merge requests' do - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, title: "Test") } let!(:merge_request_closed) { create(:merge_request, state: "closed", source_project: project, target_project: project, title: "Closed Test") } let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) } let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) } -- cgit v1.2.1 From 1883e320eafa02b332a16eec658f65c4a28def83 Mon Sep 17 00:00:00 2001 From: Luke Duncalfe Date: Fri, 5 Apr 2019 17:19:30 +1300 Subject: Use Gitlab::PushOptions for `ci.skip` push option Previously the raw push option Array was sent to Pipeline::Chain::Skip. This commit updates this class (and the chain of classes that pass the push option parameters from the API internal `post_receive` endpoint to that class) to treat push options as a Hash of options parsed by GitLab::PushOptions. The GitLab::PushOptions class takes options like this: -o ci.skip -o merge_request.create -o merge_request.target=branch and turns them into a Hash like this: { ci: { skip: true }, merge_request: { create: true, target: 'branch' } } This now how Pipeline::Chain::Skip is determining if the `ci.skip` push option was used. --- spec/lib/gitlab/push_options_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/push_options_spec.rb b/spec/lib/gitlab/push_options_spec.rb index 68b64863c21..fc9e421bea6 100644 --- a/spec/lib/gitlab/push_options_spec.rb +++ b/spec/lib/gitlab/push_options_spec.rb @@ -39,6 +39,18 @@ describe Gitlab::PushOptions do end end + describe '#as_json' do + it 'returns all options' do + options = described_class.new(['merge_request.target=value']) + + expect(options.as_json).to include( + merge_request: { + target: 'value' + } + ) + end + end + it 'can parse multiple push options' do options = described_class.new([ 'merge_request.create', -- cgit v1.2.1 From 285fcb4744ed5464aab7d2ff248e7824dd77315c Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 8 Apr 2019 19:47:23 -0300 Subject: Add methods to check dead and retrying jobs It adds two methods for checking if a background job (for a given class) has dead or retrying jobs. --- spec/lib/gitlab/background_migration_spec.rb | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb index 7d3d8a949ef..1d0ffb5e9df 100644 --- a/spec/lib/gitlab/background_migration_spec.rb +++ b/spec/lib/gitlab/background_migration_spec.rb @@ -195,4 +195,44 @@ describe Gitlab::BackgroundMigration do end end end + + describe '.dead_jobs?' do + let(:queue) do + [double(args: ['Foo', [10, 20]], queue: described_class.queue)] + end + + context 'when there are dead jobs present' do + before do + allow(Sidekiq::DeadSet).to receive(:new).and_return(queue) + end + + it 'returns true if specific job exists' do + expect(described_class.dead_jobs?('Foo')).to eq(true) + end + + it 'returns false if specific job does not exist' do + expect(described_class.dead_jobs?('Bar')).to eq(false) + end + end + end + + describe '.retrying_jobs?' do + let(:queue) do + [double(args: ['Foo', [10, 20]], queue: described_class.queue)] + end + + context 'when there are dead jobs present' do + before do + allow(Sidekiq::RetrySet).to receive(:new).and_return(queue) + end + + it 'returns true if specific job exists' do + expect(described_class.retrying_jobs?('Foo')).to eq(true) + end + + it 'returns false if specific job does not exist' do + expect(described_class.retrying_jobs?('Bar')).to eq(false) + end + end + end end -- cgit v1.2.1 From 5b7003282b6b3ce1bfc313b3271bd6827a230c34 Mon Sep 17 00:00:00 2001 From: Jason Goodman Date: Tue, 9 Apr 2019 06:52:15 +0000 Subject: Set release name when adding release notes to an existing tag Also set the release sha and author --- spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb index 082e3b36dd0..c57b96fb00d 100644 --- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb @@ -25,6 +25,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do expected = { project: project, tag: 'v1.0.0', + name: 'First release', description: 'Release v1.0.0', created_at: created_at, updated_at: created_at -- cgit v1.2.1 From 0a4f44de83fac88f39d9f4507c2fa4d935703e1b Mon Sep 17 00:00:00 2001 From: Wolphin Date: Tue, 9 Apr 2019 09:28:55 +0000 Subject: Add environment url validation --- .../lib/gitlab/ci/config/entry/environment_spec.rb | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb index 3c0007f4d57..0bc9e8bd3cd 100644 --- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb @@ -100,6 +100,26 @@ describe Gitlab::Ci::Config::Entry::Environment do end end + context 'when wrong action type is used' do + let(:config) do + { name: 'production', + action: ['stop'] } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about wrong action type' do + expect(entry.errors) + .to include 'environment action should be a string' + end + end + end + context 'when invalid action is used' do let(:config) do { name: 'production', @@ -151,6 +171,26 @@ describe Gitlab::Ci::Config::Entry::Environment do end end + context 'when wrong url type is used' do + let(:config) do + { name: 'production', + url: ['https://meow.meow'] } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about wrong url type' do + expect(entry.errors) + .to include 'environment url should be a string' + end + end + end + context 'when variables are used for environment' do let(:config) do { name: 'review/$CI_COMMIT_REF_NAME', -- cgit v1.2.1 From 0991dc8c823cdc04ec1e298651b45f7a9cf8f82a Mon Sep 17 00:00:00 2001 From: Sanad Liaquat Date: Tue, 9 Apr 2019 10:04:03 +0000 Subject: Reduce number of rspec retries In both e2e QA tests and unit tests, reduce the number of retires to 2 (i.e., 1 initial and one retry) --- spec/lib/gitlab/ci/yaml_processor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 8b39c4e4dd0..b7b30e60d44 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -615,7 +615,7 @@ module Gitlab subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) } context "when validating a ci config file with no project context" do - context "when a single string is provided" do + context "when a single string is provided", :quarantine do let(:include_content) { "/local.gitlab-ci.yml" } it "does not return any error" do -- cgit v1.2.1 From cf804218c95ce79d56bc6470135e14a505b3f9ab Mon Sep 17 00:00:00 2001 From: Daniel Wyatt Date: Tue, 9 Apr 2019 05:10:15 -0400 Subject: Add test for github project import to user namespace. --- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 8c56622e0ba..3aba744458f 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -54,6 +54,18 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do expect(project.visibility_level).to eq(namespace.visibility_level) end + + context 'when importing into a user namespace' do + subject(:service) { described_class.new(repo, repo.name, user.namespace, user, github_access_token: 'asdffg') } + + it 'sets project visibility to user namespace visibility level' do + repo.private = false + + project = service.execute + + expect(project.visibility_level).to eq(user.namespace.visibility_level) + end + end end context 'when visibility level is restricted' do -- cgit v1.2.1 From 9bc5ed14fe97fe63cd5be30c013c6af978715621 Mon Sep 17 00:00:00 2001 From: Imre Farkas Date: Tue, 9 Apr 2019 15:38:58 +0000 Subject: Move Contribution Analytics related spec in spec/features/groups/group_page_with_external_authorization_service_spec to EE --- .../gitlab/external_authorization/access_spec.rb | 142 +++++++++++++++++++++ .../gitlab/external_authorization/cache_spec.rb | 48 +++++++ .../gitlab/external_authorization/client_spec.rb | 97 ++++++++++++++ .../gitlab/external_authorization/logger_spec.rb | 45 +++++++ .../gitlab/external_authorization/response_spec.rb | 52 ++++++++ spec/lib/gitlab/external_authorization_spec.rb | 54 ++++++++ .../gitlab/import_export/safe_model_attributes.yml | 1 + 7 files changed, 439 insertions(+) create mode 100644 spec/lib/gitlab/external_authorization/access_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/cache_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/client_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/logger_spec.rb create mode 100644 spec/lib/gitlab/external_authorization/response_spec.rb create mode 100644 spec/lib/gitlab/external_authorization_spec.rb (limited to 'spec/lib') diff --git a/spec/lib/gitlab/external_authorization/access_spec.rb b/spec/lib/gitlab/external_authorization/access_spec.rb new file mode 100644 index 00000000000..5dc2521b310 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/access_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Access, :clean_gitlab_redis_cache do + subject(:access) { described_class.new(build(:user), 'dummy_label') } + + describe '#loaded?' do + it 'is `true` when it was loaded recently' do + Timecop.freeze do + allow(access).to receive(:loaded_at).and_return(5.minutes.ago) + + expect(access).to be_loaded + end + end + + it 'is `false` when there is no loading time' do + expect(access).not_to be_loaded + end + + it 'is `false` when there the result was loaded a long time ago' do + Timecop.freeze do + allow(access).to receive(:loaded_at).and_return(2.weeks.ago) + + expect(access).not_to be_loaded + end + end + end + + describe 'load!' do + let(:fake_client) { double('ExternalAuthorization::Client') } + let(:fake_response) do + double( + 'Response', + 'successful?' => true, + 'valid?' => true, + 'reason' => nil + ) + end + + before do + allow(access).to receive(:load_from_cache) + allow(fake_client).to receive(:request_access).and_return(fake_response) + allow(Gitlab::ExternalAuthorization::Client).to receive(:new) { fake_client } + end + + context 'when loading from the webservice' do + it 'loads from the webservice it the cache was empty' do + expect(access).to receive(:load_from_cache) + expect(access).to receive(:load_from_service).and_call_original + + access.load! + + expect(access).to be_loaded + end + + it 'assigns the accessibility, reason and loaded_at' do + allow(fake_response).to receive(:successful?).and_return(false) + allow(fake_response).to receive(:reason).and_return('Inaccessible label') + + access.load! + + expect(access.reason).to eq('Inaccessible label') + expect(access).not_to have_access + expect(access.loaded_at).not_to be_nil + end + + it 'returns itself' do + expect(access.load!).to eq(access) + end + + it 'stores the result in redis' do + Timecop.freeze do + fake_cache = double + expect(fake_cache).to receive(:store).with(true, nil, Time.now) + expect(access).to receive(:cache).and_return(fake_cache) + + access.load! + end + end + + context 'when the request fails' do + before do + allow(fake_client).to receive(:request_access) do + raise ::Gitlab::ExternalAuthorization::RequestFailed.new('Service unavailable') + end + end + + it 'is loaded' do + access.load! + + expect(access).to be_loaded + end + + it 'assigns the correct accessibility, reason and loaded_at' do + access.load! + + expect(access.reason).to eq('Service unavailable') + expect(access).not_to have_access + expect(access.loaded_at).not_to be_nil + end + + it 'does not store the result in redis' do + fake_cache = double + expect(fake_cache).not_to receive(:store) + allow(access).to receive(:cache).and_return(fake_cache) + + access.load! + end + end + end + + context 'When loading from cache' do + let(:fake_cache) { double('ExternalAuthorization::Cache') } + + before do + allow(access).to receive(:cache).and_return(fake_cache) + end + + it 'does not load from the webservice' do + Timecop.freeze do + expect(fake_cache).to receive(:load).and_return([true, nil, Time.now]) + + expect(access).to receive(:load_from_cache).and_call_original + expect(access).not_to receive(:load_from_service) + + access.load! + end + end + + it 'loads from the webservice when the cached result was too old' do + Timecop.freeze do + expect(fake_cache).to receive(:load).and_return([true, nil, 2.days.ago]) + + expect(access).to receive(:load_from_cache).and_call_original + expect(access).to receive(:load_from_service).and_call_original + allow(fake_cache).to receive(:store) + + access.load! + end + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/cache_spec.rb b/spec/lib/gitlab/external_authorization/cache_spec.rb new file mode 100644 index 00000000000..58e7d626707 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/cache_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Cache, :clean_gitlab_redis_cache do + let(:user) { build_stubbed(:user) } + let(:cache_key) { "external_authorization:user-#{user.id}:label-dummy_label" } + + subject(:cache) { described_class.new(user, 'dummy_label') } + + def read_from_redis(key) + Gitlab::Redis::Cache.with do |redis| + redis.hget(cache_key, key) + end + end + + def set_in_redis(key, value) + Gitlab::Redis::Cache.with do |redis| + redis.hmset(cache_key, key, value) + end + end + + describe '#load' do + it 'reads stored info from redis' do + Timecop.freeze do + set_in_redis(:access, false) + set_in_redis(:reason, 'Access denied for now') + set_in_redis(:refreshed_at, Time.now) + + access, reason, refreshed_at = cache.load + + expect(access).to eq(false) + expect(reason).to eq('Access denied for now') + expect(refreshed_at).to be_within(1.second).of(Time.now) + end + end + end + + describe '#store' do + it 'sets the values in redis' do + Timecop.freeze do + cache.store(true, 'the reason', Time.now) + + expect(read_from_redis(:access)).to eq('true') + expect(read_from_redis(:reason)).to eq('the reason') + expect(read_from_redis(:refreshed_at)).to eq(Time.now.to_s) + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/client_spec.rb b/spec/lib/gitlab/external_authorization/client_spec.rb new file mode 100644 index 00000000000..fa18c1e56e8 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/client_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Client do + let(:user) { build(:user, email: 'dummy_user@example.com') } + let(:dummy_url) { 'https://dummy.net/' } + subject(:client) { described_class.new(user, 'dummy_label') } + + before do + stub_application_setting(external_authorization_service_url: dummy_url) + end + + describe '#request_access' do + it 'performs requests to the configured endpoint' do + expect(Excon).to receive(:post).with(dummy_url, any_args) + + client.request_access + end + + it 'adds the correct params for the user to the body of the request' do + expected_body = { + user_identifier: 'dummy_user@example.com', + project_classification_label: 'dummy_label' + }.to_json + expect(Excon).to receive(:post) + .with(dummy_url, hash_including(body: expected_body)) + + client.request_access + end + + it 'respects the the timeout' do + stub_application_setting( + external_authorization_service_timeout: 3 + ) + + expect(Excon).to receive(:post).with(dummy_url, + hash_including( + connect_timeout: 3, + read_timeout: 3, + write_timeout: 3 + )) + + client.request_access + end + + it 'adds the mutual tls params when they are present' do + stub_application_setting( + external_auth_client_cert: 'the certificate data', + external_auth_client_key: 'the key data', + external_auth_client_key_pass: 'open sesame' + ) + expected_params = { + client_cert_data: 'the certificate data', + client_key_data: 'the key data', + client_key_pass: 'open sesame' + } + + expect(Excon).to receive(:post).with(dummy_url, hash_including(expected_params)) + + client.request_access + end + + it 'returns an expected response' do + expect(Excon).to receive(:post) + + expect(client.request_access) + .to be_kind_of(::Gitlab::ExternalAuthorization::Response) + end + + it 'wraps exceptions if the request fails' do + expect(Excon).to receive(:post) { raise Excon::Error.new('the request broke') } + + expect { client.request_access } + .to raise_error(::Gitlab::ExternalAuthorization::RequestFailed) + end + + describe 'for ldap users' do + let(:user) do + create(:omniauth_user, + email: 'dummy_user@example.com', + extern_uid: 'external id', + provider: 'ldapprovider') + end + + it 'includes the ldap dn for ldap users' do + expected_body = { + user_identifier: 'dummy_user@example.com', + project_classification_label: 'dummy_label', + user_ldap_dn: 'external id' + }.to_json + expect(Excon).to receive(:post) + .with(dummy_url, hash_including(body: expected_body)) + + client.request_access + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization/logger_spec.rb b/spec/lib/gitlab/external_authorization/logger_spec.rb new file mode 100644 index 00000000000..81f1b2390e6 --- /dev/null +++ b/spec/lib/gitlab/external_authorization/logger_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Logger do + let(:request_time) { Time.parse('2018-03-26 20:22:15') } + + def fake_access(has_access, user, load_type = :request) + access = double('access') + allow(access).to receive_messages(user: user, + has_access?: has_access, + loaded_at: request_time, + label: 'dummy_label', + load_type: load_type) + + access + end + + describe '.log_access' do + it 'logs a nice message for an access request' do + expected_message = "GRANTED admin@example.com access to 'dummy_label' (the/project/path)" + fake_access = fake_access(true, build(:user, email: 'admin@example.com')) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, 'the/project/path') + end + + it 'does not trip without a project path' do + expected_message = "DENIED admin@example.com access to 'dummy_label'" + fake_access = fake_access(false, build(:user, email: 'admin@example.com')) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, nil) + end + + it 'adds the load time for cached accesses' do + expected_message = "DENIED admin@example.com access to 'dummy_label' - cache #{request_time}" + fake_access = fake_access(false, build(:user, email: 'admin@example.com'), :cache) + + expect(described_class).to receive(:info).with(expected_message) + + described_class.log_access(fake_access, nil) + end + end +end diff --git a/spec/lib/gitlab/external_authorization/response_spec.rb b/spec/lib/gitlab/external_authorization/response_spec.rb new file mode 100644 index 00000000000..43211043eca --- /dev/null +++ b/spec/lib/gitlab/external_authorization/response_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization::Response do + let(:excon_response) { double } + subject(:response) { described_class.new(excon_response) } + + describe '#valid?' do + it 'is valid for 200, 401, and 403 responses' do + [200, 401, 403].each do |status| + allow(excon_response).to receive(:status).and_return(status) + + expect(response).to be_valid + end + end + + it "is invalid for other statuses" do + expect(excon_response).to receive(:status).and_return(500) + + expect(response).not_to be_valid + end + end + + describe '#reason' do + it 'returns a reason if it was included in the response body' do + expect(excon_response).to receive(:body).and_return({ reason: 'Not authorized' }.to_json) + + expect(response.reason).to eq('Not authorized') + end + + it 'returns nil when there was no body' do + expect(excon_response).to receive(:body).and_return('') + + expect(response.reason).to eq(nil) + end + end + + describe '#successful?' do + it 'is `true` if the status is 200' do + allow(excon_response).to receive(:status).and_return(200) + + expect(response).to be_successful + end + + it 'is `false` if the status is 401 or 403' do + [401, 403].each do |status| + allow(excon_response).to receive(:status).and_return(status) + + expect(response).not_to be_successful + end + end + end +end diff --git a/spec/lib/gitlab/external_authorization_spec.rb b/spec/lib/gitlab/external_authorization_spec.rb new file mode 100644 index 00000000000..7394fbfe0ce --- /dev/null +++ b/spec/lib/gitlab/external_authorization_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::ExternalAuthorization, :request_store do + include ExternalAuthorizationServiceHelpers + + let(:user) { build(:user) } + let(:label) { 'dummy_label' } + + describe '#access_allowed?' do + it 'is always true when the feature is disabled' do + # Not using `stub_application_setting` because the method is prepended in + # `EE::ApplicationSetting` which breaks when using `any_instance` + # https://gitlab.com/gitlab-org/gitlab-ce/issues/33587 + expect(::Gitlab::CurrentSettings.current_application_settings) + .to receive(:external_authorization_service_enabled) { false } + + expect(described_class).not_to receive(:access_for_user_to_label) + + expect(described_class.access_allowed?(user, label)).to be_truthy + end + end + + describe '#rejection_reason' do + it 'is always nil when the feature is disabled' do + expect(::Gitlab::CurrentSettings.current_application_settings) + .to receive(:external_authorization_service_enabled) { false } + + expect(described_class).not_to receive(:access_for_user_to_label) + + expect(described_class.rejection_reason(user, label)).to be_nil + end + end + + describe '#access_for_user_to_label' do + it 'only loads the access once per request' do + enable_external_authorization_service_check + + expect(::Gitlab::ExternalAuthorization::Access) + .to receive(:new).with(user, label).once.and_call_original + + 2.times { described_class.access_for_user_to_label(user, label, nil) } + end + + it 'logs the access request once per request' do + expect(::Gitlab::ExternalAuthorization::Logger) + .to receive(:log_access) + .with(an_instance_of(::Gitlab::ExternalAuthorization::Access), + 'the/project/path') + .once + + 2.times { described_class.access_for_user_to_label(user, label, 'the/project/path') } + end + end +end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 06995604a24..ebb62124cb1 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -496,6 +496,7 @@ Project: - merge_requests_ff_only_enabled - merge_requests_rebase_enabled - jobs_cache_index +- external_authorization_classification_label - pages_https_only Author: - name -- cgit v1.2.1 From 15e2b5ad0b2bdd0ccc0e5ebd47b78c0cd064dbea Mon Sep 17 00:00:00 2001 From: Daniel Wyatt Date: Tue, 9 Apr 2019 11:51:01 -0400 Subject: Address style review comment --- spec/lib/gitlab/legacy_github_import/project_creator_spec.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb index 3aba744458f..8675d8691c8 100644 --- a/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/project_creator_spec.rb @@ -49,7 +49,6 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do context 'when GitHub project is public' do it 'sets project visibility to namespace visibility level' do repo.private = false - project = service.execute expect(project.visibility_level).to eq(namespace.visibility_level) @@ -60,7 +59,6 @@ describe Gitlab::LegacyGithubImport::ProjectCreator do it 'sets project visibility to user namespace visibility level' do repo.private = false - project = service.execute expect(project.visibility_level).to eq(user.namespace.visibility_level) -- cgit v1.2.1 From c239bfcb1750794ec1bf8172dfa380dea64fe4c1 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Wed, 10 Apr 2019 06:38:27 +0000 Subject: Add more info logging to cluster apps Log events so that it's easy to see when different requests are starting. --- spec/lib/gitlab/kubernetes/namespace_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'spec/lib') diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb index e1c35c355f4..e91a755aa03 100644 --- a/spec/lib/gitlab/kubernetes/namespace_spec.rb +++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb @@ -62,5 +62,32 @@ describe Gitlab::Kubernetes::Namespace do subject.ensure_exists! end + + context 'when client errors' do + let(:exception) { Kubeclient::HttpError.new(500, 'system failure', nil) } + + before do + allow(client).to receive(:get_namespace).with(name).once.and_raise(exception) + end + + it 'raises the exception' do + expect { subject.ensure_exists! }.to raise_error(exception) + end + + it 'logs the error' do + expect(subject.send(:logger)).to receive(:error).with( + hash_including( + exception: 'Kubeclient::HttpError', + status_code: 500, + namespace: 'a_namespace', + class_name: 'Gitlab::Kubernetes::Namespace', + event: :failed_to_create_namespace, + message: 'system failure' + ) + ) + + expect { subject.ensure_exists! }.to raise_error(exception) + end + end end end -- cgit v1.2.1