diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-02 18:10:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-02 18:10:54 +0300 |
commit | 8e3523281051490ff696bfd85bea1195c046c87c (patch) | |
tree | e44c77aa24136950679151ae70ad6e186987602a /spec | |
parent | e2d4c85dec083d517822e3a21165cd373b162d9b (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
59 files changed, 1354 insertions, 249 deletions
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb index 732d20666cb..6c423097e70 100644 --- a/spec/controllers/admin/applications_controller_spec.rb +++ b/spec/controllers/admin/applications_controller_spec.rb @@ -40,7 +40,7 @@ RSpec.describe Admin::ApplicationsController do describe 'POST #create' do it 'creates the application' do - create_params = attributes_for(:application, trusted: true, confidential: false) + create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api']) expect do post :create, params: { doorkeeper_application: create_params } @@ -63,7 +63,7 @@ RSpec.describe Admin::ApplicationsController do context 'when the params are for a confidential application' do it 'creates a confidential application' do - create_params = attributes_for(:application, confidential: true) + create_params = attributes_for(:application, confidential: true, scopes: ['read_user']) expect do post :create, params: { doorkeeper_application: create_params } @@ -75,6 +75,18 @@ RSpec.describe Admin::ApplicationsController do expect(application).to have_attributes(create_params.except(:uid, :owner_type)) end end + + context 'when scopes are not present' do + it 'renders the application form on errors' do + create_params = attributes_for(:application, trusted: true, confidential: false) + + expect do + post :create, params: { doorkeeper_application: create_params } + end.not_to change { Doorkeeper::Application.count } + + expect(response).to render_template :new + end + end end describe 'PATCH #update' do diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index 2e0ee671d3f..d2a569a9d48 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -99,7 +99,9 @@ RSpec.describe Admin::ClustersController do end describe 'GET #new' do - def get_new(provider: 'gcp') + let(:user) { admin } + + def go(provider: 'gcp') get :new, params: { provider: provider } end @@ -112,7 +114,7 @@ RSpec.describe Admin::ClustersController do context 'when selected provider is gke and no valid gcp token exists' do it 'redirects to gcp authorize_url' do - get_new + go expect(response).to redirect_to(assigns(:authorize_url)) end @@ -125,7 +127,7 @@ RSpec.describe Admin::ClustersController do end it 'does not have authorize_url' do - get_new + go expect(assigns(:authorize_url)).to be_nil end @@ -137,7 +139,7 @@ RSpec.describe Admin::ClustersController do end it 'has new object' do - get_new + go expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) end @@ -158,16 +160,18 @@ RSpec.describe Admin::ClustersController do describe 'functionality for existing cluster' do it 'has new object' do - get_new + go expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) end end + include_examples 'GET new cluster shared examples' + describe 'security' do - it { expect { get_new }.to be_allowed_for(:admin) } - it { expect { get_new }.to be_denied_for(:user) } - it { expect { get_new }.to be_denied_for(:external) } + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } end end @@ -424,14 +428,13 @@ RSpec.describe Admin::ClustersController do end describe 'POST authorize AWS role for EKS cluster' do - let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } - let(:role_external_id) { '12345' } + let!(:role) { create(:aws_role, user: admin) } + let(:role_arn) { 'arn:new-role' } let(:params) do { cluster: { - role_arn: role_arn, - role_external_id: role_external_id + role_arn: role_arn } } end @@ -445,28 +448,32 @@ RSpec.describe Admin::ClustersController do .and_return(double(execute: double)) end - it 'creates an Aws::Role record' do - expect { go }.to change { Aws::Role.count } + it 'updates the associated role with the supplied ARN' do + go expect(response).to have_gitlab_http_status(:ok) - - role = Aws::Role.last - expect(role.user).to eq admin - expect(role.role_arn).to eq role_arn - expect(role.role_external_id).to eq role_external_id + expect(role.reload.role_arn).to eq(role_arn) end - context 'role cannot be created' do + context 'supplied role is invalid' do let(:role_arn) { 'invalid-role' } - it 'does not create a record' do - expect { go }.not_to change { Aws::Role.count } + it 'does not update the associated role' do + expect { go }.not_to change { role.role_arn } expect(response).to have_gitlab_http_status(:unprocessable_entity) end end describe 'security' do + before do + allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service| + response = double(status: :ok, body: double) + + allow(service).to receive(:execute).and_return(response) + end + end + it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:external) } diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 357044a144c..f8d4690e9ce 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -229,6 +229,7 @@ RSpec.describe ApplicationController do it 'does not redirect if 2FA is not required' do allow(controller).to receive(:two_factor_authentication_required?).and_return(false) + allow(controller).to receive(:current_user).and_return(create(:user)) expect(controller).not_to receive(:redirect_to) @@ -346,13 +347,17 @@ RSpec.describe ApplicationController do let(:user) { create :user, otp_grace_period_started_at: 2.hours.ago } it 'returns true if the grace period has expired' do - allow(controller).to receive(:two_factor_grace_period).and_return(1) + allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier| + allow(verifier).to receive(:two_factor_grace_period).and_return(2) + end expect(subject).to be_truthy end it 'returns false if the grace period is still active' do - allow(controller).to receive(:two_factor_grace_period).and_return(3) + allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier| + allow(verifier).to receive(:two_factor_grace_period).and_return(3) + end expect(subject).to be_falsey end diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 1593e1290c4..81d5bc7770f 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -180,6 +180,8 @@ RSpec.describe Groups::ClustersController do end end + include_examples 'GET new cluster shared examples' + describe 'security' do it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:owner).of(group) } @@ -493,14 +495,13 @@ RSpec.describe Groups::ClustersController do end describe 'POST authorize AWS role for EKS cluster' do - let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } - let(:role_external_id) { '12345' } + let!(:role) { create(:aws_role, user: user) } + let(:role_arn) { 'arn:new-role' } let(:params) do { cluster: { - role_arn: role_arn, - role_external_id: role_external_id + role_arn: role_arn } } end @@ -514,28 +515,32 @@ RSpec.describe Groups::ClustersController do .and_return(double(execute: double)) end - it 'creates an Aws::Role record' do - expect { go }.to change { Aws::Role.count } + it 'updates the associated role with the supplied ARN' do + go expect(response).to have_gitlab_http_status(:ok) - - role = Aws::Role.last - expect(role.user).to eq user - expect(role.role_arn).to eq role_arn - expect(role.role_external_id).to eq role_external_id + expect(role.reload.role_arn).to eq(role_arn) end - context 'role cannot be created' do + context 'supplied role is invalid' do let(:role_arn) { 'invalid-role' } - it 'does not create a record' do - expect { go }.not_to change { Aws::Role.count } + it 'does not update the associated role' do + expect { go }.not_to change { role.role_arn } expect(response).to have_gitlab_http_status(:unprocessable_entity) end end describe 'security' do + before do + allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service| + response = double(status: :ok, body: double) + + allow(service).to receive(:execute).and_return(response) + end + end + it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:owner).of(group) } it { expect { go }.to be_allowed_for(:maintainer).of(group) } diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb index 0a7975b8c1b..f21ef324884 100644 --- a/spec/controllers/oauth/applications_controller_spec.rb +++ b/spec/controllers/oauth/applications_controller_spec.rb @@ -123,7 +123,8 @@ RSpec.describe Oauth::ApplicationsController do invalid_uri_params = { doorkeeper_application: { name: 'foo', - redirect_uri: 'javascript://alert()' + redirect_uri: 'javascript://alert()', + scopes: ['api'] } } @@ -133,6 +134,23 @@ RSpec.describe Oauth::ApplicationsController do end end + context 'when scopes are not present' do + render_views + + it 'shows an error for blank scopes' do + invalid_uri_params = { + doorkeeper_application: { + name: 'foo', + redirect_uri: 'http://example.org' + } + } + + post :create, params: invalid_uri_params + + expect(response.body).to include 'Scopes can't be blank' + end + end + it_behaves_like 'redirects to login page when the user is not signed in' it_behaves_like 'redirects to 2fa setup page when the user requires it' end @@ -172,7 +190,8 @@ RSpec.describe Oauth::ApplicationsController do { doorkeeper_application: { name: 'foo', - redirect_uri: 'http://example.org' + redirect_uri: 'http://example.org', + scopes: ['api'] } } end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index dce996b977d..3f7f0c55f38 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -40,6 +40,22 @@ RSpec.describe OmniauthCallbacksController, type: :controller do end end + context 'when sign in is not valid' do + let(:provider) { :github } + let(:extern_uid) { 'my-uid' } + + it 'renders omniauth error page' do + allow_next_instance_of(Gitlab::Auth::OAuth::User) do |instance| + allow(instance).to receive(:valid_sign_in?).and_return(false) + end + + post provider + + expect(response).to render_template("errors/omniauth_error") + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + context 'when the user is on the last sign in attempt' do let(:extern_uid) { 'my-uid' } diff --git a/spec/controllers/profiles/active_sessions_controller_spec.rb b/spec/controllers/profiles/active_sessions_controller_spec.rb new file mode 100644 index 00000000000..f54f69d853d --- /dev/null +++ b/spec/controllers/profiles/active_sessions_controller_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Profiles::ActiveSessionsController do + describe 'DELETE destroy' do + let_it_be(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'invalidates all remember user tokens' do + ActiveSession.set(user, request) + session_id = request.session.id.public_id + user.remember_me! + + delete :destroy, params: { id: session_id } + + expect(user.reload.remember_created_at).to be_nil + end + end +end diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index e6839f54c5d..59eb33f4bc6 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -14,10 +14,9 @@ RSpec.describe Profiles::TwoFactorAuthsController do let(:user) { create(:user) } it 'generates otp_secret for user' do - expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once + expect(User).to receive(:generate_otp_secret).with(32).and_call_original.once get :show - get :show # Second hit shouldn't re-generate it end it 'assigns qr_code' do @@ -27,6 +26,14 @@ RSpec.describe Profiles::TwoFactorAuthsController do get :show expect(assigns[:qr_code]).to eq code end + + it 'generates a unique otp_secret every time the page is loaded' do + expect(User).to receive(:generate_otp_secret).with(32).and_call_original.twice + + 2.times do + get :show + end + end end describe 'POST create' do @@ -57,6 +64,12 @@ RSpec.describe Profiles::TwoFactorAuthsController do expect(assigns[:codes]).to match_array %w(a b c) end + it 'calls to delete other sessions' do + expect(ActiveSession).to receive(:destroy_all_but_current) + + go + end + it 'renders create' do go expect(response).to render_template(:create) diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index da4faad2a39..51a451570c5 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -183,6 +183,8 @@ RSpec.describe Projects::ClustersController do end end + include_examples 'GET new cluster shared examples' + describe 'security' do it 'is allowed for admin when admin mode enabled', :enable_admin_mode do expect { go }.to be_allowed_for(:admin) @@ -521,14 +523,13 @@ RSpec.describe Projects::ClustersController do end describe 'POST authorize AWS role for EKS cluster' do - let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' } - let(:role_external_id) { '12345' } + let!(:role) { create(:aws_role, user: user) } + let(:role_arn) { 'arn:new-role' } let(:params) do { cluster: { - role_arn: role_arn, - role_external_id: role_external_id + role_arn: role_arn } } end @@ -542,28 +543,32 @@ RSpec.describe Projects::ClustersController do .and_return(double(execute: double)) end - it 'creates an Aws::Role record' do - expect { go }.to change { Aws::Role.count } + it 'updates the associated role with the supplied ARN' do + go expect(response).to have_gitlab_http_status(:ok) - - role = Aws::Role.last - expect(role.user).to eq user - expect(role.role_arn).to eq role_arn - expect(role.role_external_id).to eq role_external_id + expect(role.reload.role_arn).to eq(role_arn) end - context 'role cannot be created' do + context 'supplied role is invalid' do let(:role_arn) { 'invalid-role' } - it 'does not create a record' do - expect { go }.not_to change { Aws::Role.count } + it 'does not update the associated role' do + expect { go }.not_to change { role.role_arn } expect(response).to have_gitlab_http_status(:unprocessable_entity) end end describe 'security' do + before do + allow_next_instance_of(Clusters::Aws::AuthorizeRoleService) do |service| + response = double(status: :ok, body: double) + + allow(service).to receive(:execute).and_return(response) + end + end + it 'is allowed for admin when admin mode enabled', :enable_admin_mode do expect { go }.to be_allowed_for(:admin) end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index d66a0246c2d..4d325f55fb0 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -149,9 +149,15 @@ RSpec.describe SearchController do expect(assigns[:search_objects].first).to eq note end - it_behaves_like 'tracking unique hll events', :show do - let(:request_params) { { scope: 'projects', search: 'term' } } - let(:target_id) { 'i_search_total' } + context 'unique users tracking' do + before do + allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event) + end + + it_behaves_like 'tracking unique hll events', :show do + let(:request_params) { { scope: 'projects', search: 'term' } } + let(:target_id) { 'i_search_total' } + end end context 'on restricted projects' do diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 7754fac2fb2..acec1cf22fd 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -261,8 +261,8 @@ RSpec.describe SessionsController do context 'when using two-factor authentication via OTP' do let(:user) { create(:user, :two_factor) } - def authenticate_2fa(user_params) - post(:create, params: { user: user_params }, session: { otp_user_id: user.id }) + def authenticate_2fa(user_params, otp_user_id: user.id) + post(:create, params: { user: user_params }, session: { otp_user_id: otp_user_id }) end context 'remember_me field' do @@ -299,8 +299,22 @@ RSpec.describe SessionsController do end end + # See issue gitlab-org/gitlab#20302. + context 'when otp_user_id is stale' do + render_views + + it 'favors login over otp_user_id when password is present and does not authenticate the user' do + authenticate_2fa( + { login: 'random_username', password: user.password }, + otp_user_id: user.id + ) + + expect(response).to set_flash.now[:alert].to /Invalid Login or password/ + end + end + ## - # See #14900 issue + # See issue gitlab-org/gitlab-foss#14900 # context 'when authenticating with login and OTP of another user' do context 'when another user has 2FA enabled' do @@ -386,18 +400,6 @@ RSpec.describe SessionsController do end end end - - context 'when another user does not have 2FA enabled' do - let(:another_user) { create(:user) } - - it 'does not leak that 2FA is disabled for another user' do - authenticate_2fa(login: another_user.username, - otp_attempt: 'invalid') - - expect(response).to set_flash.now[:alert] - .to /Invalid two-factor code/ - end - end end end diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb index 3f3d71e842c..7a9a6f2ccb8 100644 --- a/spec/features/admin/admin_manage_applications_spec.rb +++ b/spec/features/admin/admin_manage_applications_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'admin manage applications' do sign_in(create(:admin)) end - it do + it 'creates new oauth application' do visit admin_applications_path click_on 'New application' @@ -16,6 +16,7 @@ RSpec.describe 'admin manage applications' do fill_in :doorkeeper_application_name, with: 'test' fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' check :doorkeeper_application_trusted + check :doorkeeper_application_scopes_read_user click_on 'Submit' expect(page).to have_content('Application: test') expect(page).to have_content('Application ID') @@ -43,4 +44,19 @@ RSpec.describe 'admin manage applications' do end expect(page.find('.oauth-applications')).not_to have_content('test_changed') end + + context 'when scopes are blank' do + it 'returns an error' do + visit admin_applications_path + + click_on 'New application' + expect(page).to have_content('New application') + + fill_in :doorkeeper_application_name, with: 'test' + fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + click_on 'Submit' + + expect(page).to have_content("Scopes can't be blank") + end + end end diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb index 666fbe84fae..1512d539dec 100644 --- a/spec/features/issues/service_desk_spec.rb +++ b/spec/features/issues/service_desk_spec.rb @@ -109,6 +109,14 @@ RSpec.describe 'Service Desk Issue Tracker', :js do find('.input-token .filtered-search').native.send_key(:backspace) expect(page).to have_selector('.js-visual-token', count: 1) end + + it 'support bot author token has been properly added' do + within('.filtered-search-token') do + expect(page).to have_selector('.name', count: 1, visible: false) + expect(page).to have_selector('.operator', count: 1, visible: false) + expect(page).to have_selector('.value-container', count: 1, visible: false) + end + end end end end diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index 57362ed2d54..fbf4e531db1 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -30,13 +30,11 @@ RSpec.describe 'Copy as GFM', :js do it 'works', :aggregate_failures do verify( 'nesting', - '> 1. [x] **[$`2 + 2`$ {-=-}{+=+} 2^2 ~~:thumbsup:~~](http://google.com)**' ) verify( 'a real world example from the gitlab-ce README', - <<~GFM # GitLab @@ -103,19 +101,16 @@ RSpec.describe 'Copy as GFM', :js do verify( 'InlineDiffFilter', - '{-Deleted text-}', '{+Added text+}' ) verify( 'TaskListFilter', - <<~GFM, * [ ] Unchecked task * [x] Checked task GFM - <<~GFM 1. [ ] Unchecked ordered task 1. [x] Checked ordered task @@ -124,7 +119,6 @@ RSpec.describe 'Copy as GFM', :js do verify( 'ReferenceFilter', - # issue reference @feat.issue.to_reference, # full issue reference @@ -141,13 +135,11 @@ RSpec.describe 'Copy as GFM', :js do verify( 'AutolinkFilter', - 'https://example.com' ) verify( 'TableOfContentsFilter', - <<~GFM, [[_TOC_]] @@ -155,64 +147,53 @@ RSpec.describe 'Copy as GFM', :js do ## Heading 2 GFM - pipeline: :wiki, wiki: @project.wiki ) verify( 'EmojiFilter', - ':thumbsup:' ) verify( 'ImageLinkFilter', - '![Image](https://example.com/image.png)' ) verify_media_with_partial_path( '[test.txt](/uploads/a123/image.txt)', - project_media_uri(@project, '/uploads/a123/image.txt') ) verify_media_with_partial_path( '![Image](/uploads/a123/image.png)', - project_media_uri(@project, '/uploads/a123/image.png') ) verify( 'VideoLinkFilter', - '![Video](https://example.com/video.mp4)' ) verify_media_with_partial_path( '![Video](/uploads/a123/video.mp4)', - project_media_uri(@project, '/uploads/a123/video.mp4') ) verify( 'AudioLinkFilter', - '![Audio](https://example.com/audio.wav)' ) verify_media_with_partial_path( '![Audio](/uploads/a123/audio.wav)', - project_media_uri(@project, '/uploads/a123/audio.wav') ) verify( 'MathFilter: math as converted from GFM to HTML', - '$`c = \pm\sqrt{a^2 + b^2}`$', - # math block <<~GFM ```math @@ -334,7 +315,6 @@ RSpec.describe 'Copy as GFM', :js do verify( 'MermaidFilter: mermaid as converted from GFM to HTML', - <<~GFM ```mermaid graph TD; @@ -429,7 +409,6 @@ RSpec.describe 'Copy as GFM', :js do verify( 'SuggestionFilter: suggestion as converted from GFM to HTML', - <<~GFM ```suggestion New @@ -491,7 +470,6 @@ RSpec.describe 'Copy as GFM', :js do verify( 'SanitizationFilter', - <<~GFM <sub>sub</sub> @@ -527,13 +505,11 @@ RSpec.describe 'Copy as GFM', :js do verify( 'SanitizationFilter', - <<~GFM, ``` Plain text ``` GFM - <<~GFM, ```ruby def foo @@ -541,7 +517,6 @@ RSpec.describe 'Copy as GFM', :js do end ``` GFM - <<~GFM Foo @@ -553,27 +528,19 @@ RSpec.describe 'Copy as GFM', :js do verify( 'MarkdownFilter', - "Line with two spaces at the end \nto insert a linebreak", - '`code`', '`` code with ` ticks ``', - '> Quote', - # multiline quote <<~GFM, > Multiline Quote > > With multiple paragraphs GFM - '![Image](https://example.com/image.png)', - '# Heading with no anchor link', - '[Link](https://example.com)', - <<~GFM, * List item * List item 2 @@ -598,7 +565,6 @@ RSpec.describe 'Copy as GFM', :js do > Blockquote GFM - <<~GFM, 1. Ordered list item 1. Ordered list item 2 @@ -623,22 +589,16 @@ RSpec.describe 'Copy as GFM', :js do --- GFM - '# Heading', '## Heading', '### Heading', '#### Heading', '##### Heading', '###### Heading', - '**Bold**', - '*Italics*', - '~~Strikethrough~~', - '---', - # table <<~GFM, | Centered | Right | Left | @@ -696,9 +656,7 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as inline code' do verify( '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"] .line .no', - '`RuntimeError`', - target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' ) end @@ -708,9 +666,7 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as inline code' do verify( '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]', - '`raise RuntimeError, "System commands must be given as an array of strings"`', - target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' ) end @@ -720,14 +676,12 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as a code block' do verify( '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', - <<~GFM, ```ruby raise RuntimeError, "System commands must be given as an array of strings" end ``` GFM - target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]' ) end @@ -755,7 +709,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as a code block' do verify( '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', - <<~GFM, ```ruby unless cmd.is_a?(Array) @@ -763,7 +716,6 @@ RSpec.describe 'Copy as GFM', :js do end ``` GFM - target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].left-side' ) end @@ -773,7 +725,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as a code block' do verify( '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], [id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"]', - <<~GFM, ```ruby unless cmd.is_a?(Array) @@ -781,7 +732,6 @@ RSpec.describe 'Copy as GFM', :js do end ``` GFM - target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"].right-side' ) end @@ -799,7 +749,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as inline code' do verify( '.line[id="LC9"] .no', - '`RuntimeError`' ) end @@ -809,7 +758,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as inline code' do verify( '.line[id="LC9"]', - '`raise RuntimeError, "System commands must be given as an array of strings"`' ) end @@ -819,7 +767,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as a code block' do verify( '.line[id="LC9"], .line[id="LC10"]', - <<~GFM ```ruby raise RuntimeError, "System commands must be given as an array of strings" @@ -841,7 +788,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as inline code' do verify( '.line[id="LC27"] .nl', - '`"bio"`' ) end @@ -851,7 +797,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as inline code' do verify( '.line[id="LC27"]', - '`"bio": null,`' ) end @@ -861,7 +806,6 @@ RSpec.describe 'Copy as GFM', :js do it 'copies as a code block with the correct language' do verify( '.line[id="LC27"], .line[id="LC28"]', - <<~GFM ```json "bio": null, diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb index d65365db880..22eed748c00 100644 --- a/spec/features/profiles/user_manages_applications_spec.rb +++ b/spec/features/profiles/user_manages_applications_spec.rb @@ -15,6 +15,7 @@ RSpec.describe 'User manages applications' do fill_in :doorkeeper_application_name, with: 'test' fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + check :doorkeeper_application_scopes_read_user click_on 'Save application' expect(page).to have_content 'Application: test' @@ -41,4 +42,16 @@ RSpec.describe 'User manages applications' do end expect(page.find('.oauth-applications')).not_to have_content 'test_changed' end + + context 'when scopes are blank' do + it 'returns an error' do + expect(page).to have_content 'Add new application' + + fill_in :doorkeeper_application_name, with: 'test' + fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + click_on 'Save application' + + expect(page).to have_content("Scopes can't be blank") + end + end end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index a2fd1caf6a3..853c381fe6b 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -177,6 +177,14 @@ RSpec.describe 'Login' do expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) end + it 'does not allow sign-in if the user password is updated before entering a one-time code' do + user.update!(password: 'new_password') + + enter_code(user.current_otp) + + expect(page).to have_content('An error occurred. Please sign in again.') + end + context 'using one-time code' do it 'allows login with valid code' do expect(authentication_metrics) @@ -232,7 +240,7 @@ RSpec.describe 'Login' do expect(codes.size).to eq 10 # Ensure the generated codes get saved - user.save + user.save(touch: false) end context 'with valid code' do @@ -290,7 +298,7 @@ RSpec.describe 'Login' do code = codes.sample expect(user.invalidate_otp_backup_code!(code)).to eq true - user.save! + user.save!(touch: false) expect(user.reload.otp_backup_codes.size).to eq 9 enter_code(code) diff --git a/spec/finders/ci/auth_job_finder_spec.rb b/spec/finders/ci/auth_job_finder_spec.rb new file mode 100644 index 00000000000..6cd58f5cd01 --- /dev/null +++ b/spec/finders/ci/auth_job_finder_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Ci::AuthJobFinder do + let_it_be(:job, reload: true) { create(:ci_build, status: :running) } + + let(:token) { job.token } + + subject(:finder) do + described_class.new(token: token) + end + + describe '#execute!' do + subject(:execute) { finder.execute! } + + it { is_expected.to eq(job) } + + it 'raises error if the job is not running' do + job.success! + + expect { execute }.to raise_error described_class::NotRunningJobError, 'Job is not running' + end + + it 'raises error if the job is erased' do + expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job) + expect(job).to receive(:erased?).and_return(true) + + expect { execute }.to raise_error described_class::ErasedJobError, 'Job has been erased!' + end + + it 'raises error if the the project is missing' do + expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job) + expect(job).to receive(:project).and_return(nil) + + expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!' + end + + it 'raises error if the the project is being removed' do + project = double(Project) + + expect(::Ci::Build).to receive(:find_by_token).with(job.token).and_return(job) + expect(job).to receive(:project).twice.and_return(project) + expect(project).to receive(:pending_delete?).and_return(true) + + expect { execute }.to raise_error described_class::DeletedProjectError, 'Project has been deleted!' + end + + context 'with wrong job token' do + let(:token) { 'missing' } + + it { is_expected.to be_nil } + end + end + + describe '#execute' do + subject(:execute) { finder.execute } + + before do + job.success! + end + + it { is_expected.to be_nil } + end +end diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index 559d1004b4b..ddba9b595a4 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -11,8 +11,10 @@ RSpec.describe UserRecentEventsFinder do let!(:private_event) { create(:event, project: private_project, author: project_owner) } let!(:internal_event) { create(:event, project: internal_project, author: project_owner) } let!(:public_event) { create(:event, project: public_project, author: project_owner) } + let(:limit) { nil } + let(:params) { { limit: limit } } - subject(:finder) { described_class.new(current_user, project_owner) } + subject(:finder) { described_class.new(current_user, project_owner, params) } describe '#execute' do context 'when profile is public' do @@ -48,5 +50,38 @@ RSpec.describe UserRecentEventsFinder do expect(events).to include(event_b) end end + + context 'limits' do + before do + stub_const("#{described_class}::DEFAULT_LIMIT", 1) + stub_const("#{described_class}::MAX_LIMIT", 3) + end + + context 'when limit is not set' do + it 'returns events limited to DEFAULT_LIMIT' do + expect(finder.execute.size).to eq(described_class::DEFAULT_LIMIT) + end + end + + context 'when limit is set' do + let(:limit) { 2 } + + it 'returns events limited to specified limit' do + expect(finder.execute.size).to eq(limit) + end + end + + context 'when limit is set to a number that exceeds maximum limit' do + let(:limit) { 4 } + + before do + create(:event, project: public_project, author: project_owner) + end + + it 'returns events limited to MAX_LIMIT' do + expect(finder.execute.size).to eq(described_class::MAX_LIMIT) + end + end + end end end diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 5d6aa863994..4db12643069 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -212,6 +212,55 @@ RSpec.describe GitlabSchema do end end + describe '.parse_gid' do + let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' } + + before do + test_base = Class.new + test_one = Class.new(test_base) + test_two = Class.new(test_base) + + stub_const('TestBase', test_base) + stub_const('TestOne', test_one) + stub_const('TestTwo', test_two) + end + + it 'parses the gid' do + gid = described_class.parse_gid(global_id) + + expect(gid.model_id).to eq '2147483647' + expect(gid.model_class).to eq TestOne + end + + context 'when gid is malformed' do + let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' } + + it 'raises an error' do + expect { described_class.parse_gid(global_id) } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab ID.") + end + end + + context 'when using expected_type' do + it 'accepts a single type' do + gid = described_class.parse_gid(global_id, expected_type: TestOne) + + expect(gid.model_class).to eq TestOne + end + + it 'accepts an ancestor type' do + gid = described_class.parse_gid(global_id, expected_type: TestBase) + + expect(gid.model_class).to eq TestOne + end + + it 'rejects an unknown type' do + expect { described_class.parse_gid(global_id, expected_type: TestTwo) } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestTwo.") + end + end + end + def field_instrumenters described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins] end diff --git a/spec/graphql/mutations/boards/lists/create_spec.rb b/spec/graphql/mutations/boards/lists/create_spec.rb index 1a881ac81e8..b1fe9911c7b 100644 --- a/spec/graphql/mutations/boards/lists/create_spec.rb +++ b/spec/graphql/mutations/boards/lists/create_spec.rb @@ -24,14 +24,12 @@ RSpec.describe Mutations::Boards::Lists::Create do describe '#ready?' do it 'raises an error if required arguments are missing' do expect { mutation.ready?({ board_id: 'some id' }) } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError, - 'one and only one of backlog or labelId is required') + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/) end it 'raises an error if too many required arguments are specified' do expect { mutation.ready?({ board_id: 'some id', backlog: true, label_id: 'some label' }) } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError, - 'one and only one of backlog or labelId is required') + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/) end end @@ -66,6 +64,15 @@ RSpec.describe Mutations::Boards::Lists::Create do expect(new_list.title).to eq dev_label.title expect(new_list.position).to eq 0 end + + context 'when label not found' do + let(:list_create_params) { { label_id: "gid://gitlab/Label/#{non_existing_record_id}" } } + + it 'raises an error' do + expect { subject } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Label not found!') + end + end end end diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index fcb9efa39d5..153dc19335b 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -75,7 +75,7 @@ RSpec.describe MergeRequestsHelper do describe '#tab_link_for' do let(:merge_request) { create(:merge_request, :simple) } - let(:options) { Hash.new } + let(:options) { {} } subject { tab_link_for(merge_request, :show, options) { 'Discussion' } } diff --git a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb index 70a95d7ba7b..c19b7b63eec 100644 --- a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb +++ b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb @@ -50,7 +50,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do describe '#find_job_from_http_basic_auth' do let_it_be(:user) { personal_access_token.user } - let(:job) { create(:ci_build, user: user) } + let(:job) { create(:ci_build, user: user, status: :running) } let(:password) { job.token } subject { helper.find_job_from_http_basic_auth } @@ -60,6 +60,16 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do end it_behaves_like 'invalid auth header' + + context 'when the job is not running' do + before do + job.update!(status: :failed) + end + + it_behaves_like 'valid auth header' do + let(:expected_result) { nil } + end + end end describe '#find_deploy_token_from_http_basic_auth' do diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index a73ac0b34af..1ac8ebe1369 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -37,11 +37,29 @@ RSpec.describe Gitlab::Auth::AuthFinders do expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) end - it "return user if token is valid" do - set_token(job.token) + context 'with a running job' do + before do + job.update!(status: :running) + end + + it 'return user if token is valid' do + set_token(job.token) + + expect(subject).to eq(user) + expect(@current_authenticated_job).to eq job + end + end - expect(subject).to eq(user) - expect(@current_authenticated_job).to eq job + context 'with a job that is not running' do + before do + job.update!(status: :failed) + end + + it 'returns an Unauthorized exception' do + set_token(job.token) + + expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) + end end end end @@ -557,7 +575,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do context 'with CI username' do let(:username) { ::Gitlab::Auth::CI_JOB_USER } let(:user) { create(:user) } - let(:build) { create(:ci_build, user: user) } + let(:build) { create(:ci_build, user: user, status: :running) } it 'returns nil without password' do set_basic_auth_header(username, nil) @@ -576,6 +594,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) end + + it 'returns exception if the job is not running' do + set_basic_auth_header(username, build.token) + build.success! + + expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) + end end end @@ -586,7 +611,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do context 'with a job token' do let(:route_authentication_setting) { { job_token_allowed: true } } - let(:job) { create(:ci_build, user: user) } + let(:job) { create(:ci_build, user: user, status: :running) } before do env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}" @@ -641,7 +666,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do end describe '#find_user_from_job_token' do - let(:job) { create(:ci_build, user: user) } + let(:job) { create(:ci_build, user: user, status: :running) } let(:route_authentication_setting) { { job_token_allowed: true } } subject { find_user_from_job_token } @@ -666,6 +691,13 @@ RSpec.describe Gitlab::Auth::AuthFinders do expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) end + it 'returns exception if the job is not running' do + set_header(described_class::JOB_TOKEN_HEADER, job.token) + job.success! + + expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) + end + context 'when route is not allowed to be authenticated' do let(:route_authentication_setting) { { job_token_allowed: false } } diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb index ef83321cc0e..b89ceb37076 100644 --- a/spec/lib/gitlab/auth/request_authenticator_spec.rb +++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb @@ -88,7 +88,7 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do describe '#find_user_from_job_token' do let!(:user) { build(:user) } - let!(:job) { build(:ci_build, user: user) } + let!(:job) { build(:ci_build, user: user, status: :running) } before do env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'token' @@ -97,13 +97,18 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do context 'with API requests' do before do env['SCRIPT_NAME'] = '/api/endpoint' + expect(::Ci::Build).to receive(:find_by_token).with('token').and_return(job) end it 'tries to find the user' do - expect(::Ci::Build).to receive(:find_by_token).and_return(job) - expect(subject.find_sessionless_user([:api])).to eq user end + + it 'returns nil if the job is not running' do + job.status = :success + + expect(subject.find_sessionless_user([:api])).to be_blank + end end context 'without API requests' do diff --git a/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb new file mode 100644 index 00000000000..f906870195a --- /dev/null +++ b/spec/lib/gitlab/auth/two_factor_auth_verifier_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::TwoFactorAuthVerifier do + let(:user) { create(:user) } + + subject { described_class.new(user) } + + describe '#two_factor_authentication_required?' do + describe 'when it is required on application level' do + it 'returns true' do + stub_application_setting require_two_factor_authentication: true + + expect(subject.two_factor_authentication_required?).to be_truthy + end + end + + describe 'when it is required on group level' do + it 'returns true' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + + expect(subject.two_factor_authentication_required?).to be_truthy + end + end + + describe 'when it is not required' do + it 'returns false when not required on group level' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false) + + expect(subject.two_factor_authentication_required?).to be_falsey + end + end + end + + describe '#current_user_needs_to_setup_two_factor?' do + it 'returns false when current_user is nil' do + expect(described_class.new(nil).current_user_needs_to_setup_two_factor?).to be_falsey + end + + it 'returns false when current_user does not have temp email' do + allow(user).to receive(:two_factor_enabled?).and_return(false) + allow(user).to receive(:temp_oauth_email?).and_return(true) + + expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey + end + + it 'returns false when current_user has 2fa disabled' do + allow(user).to receive(:temp_oauth_email?).and_return(false) + allow(user).to receive(:two_factor_enabled?).and_return(true) + + expect(subject.current_user_needs_to_setup_two_factor?).to be_falsey + end + + it 'returns true when user requires 2fa authentication' do + allow(user).to receive(:two_factor_enabled?).and_return(false) + allow(user).to receive(:temp_oauth_email?).and_return(false) + + expect(subject.current_user_needs_to_setup_two_factor?).to be_truthy + end + end + + describe '#two_factor_grace_period' do + it 'returns grace period from settings if there is no period from groups' do + stub_application_setting two_factor_grace_period: 2 + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(false) + + expect(subject.two_factor_grace_period).to eq(2) + end + + it 'returns grace period from groups if there is no period from settings' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + allow(user).to receive(:two_factor_grace_period).and_return(3) + + expect(subject.two_factor_grace_period).to eq(3) + end + + it 'returns minimal grace period if there is grace period from settings and from group' do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + allow(user).to receive(:two_factor_grace_period).and_return(3) + stub_application_setting two_factor_grace_period: 2 + + expect(subject.two_factor_grace_period).to eq(2) + end + end + + describe '#two_factor_grace_period_expired?' do + before do + allow(user).to receive(:otp_grace_period_started_at).and_return(4.hours.ago) + end + + it 'returns true if the grace period has expired' do + allow(subject).to receive(:two_factor_grace_period).and_return(2) + + expect(subject.two_factor_grace_period_expired?).to be_truthy + end + + it 'returns false if the grace period has not expired' do + allow(subject).to receive(:two_factor_grace_period).and_return(6) + + expect(subject.two_factor_grace_period_expired?).to be_falsey + end + + context 'when otp_grace_period_started_at is nil' do + it 'returns false' do + allow(user).to receive(:otp_grace_period_started_at).and_return(nil) + + expect(subject.two_factor_grace_period_expired?).to be_falsey + end + end + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index dcaaa8d4188..b6a8ac31074 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -441,7 +441,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do end end - shared_examples 'deploy token with disabled registry' do + shared_examples 'deploy token with disabled feature' do context 'when registry disabled' do before do stub_container_registry_config(enabled: false) @@ -452,6 +452,15 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do .to eq(auth_failure) end end + + context 'when repository is disabled' do + let(:project) { create(:project, :repository_disabled) } + + it 'fails when login and token are valid' do + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + .to eq(auth_failure) + end + end end context 'when deploy token and user have the same username' do @@ -604,7 +613,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it_behaves_like 'registry token scope' end - it_behaves_like 'deploy token with disabled registry' + it_behaves_like 'deploy token with disabled feature' end context 'when the deploy token has write_registry as a scope' do @@ -626,7 +635,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it_behaves_like 'registry token scope' end - it_behaves_like 'deploy token with disabled registry' + it_behaves_like 'deploy token with disabled feature' end end end @@ -682,12 +691,69 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do expect( gl_auth.find_with_user_password(username, password) ).not_to eql user end + it 'does not find user in locked state' do + user.lock_access! + + expect(gl_auth.find_with_user_password(username, password)).not_to eql user + end + it "does not find user in ldap_blocked state" do user.ldap_block expect( gl_auth.find_with_user_password(username, password) ).not_to eql user end + context 'with increment_failed_attempts' do + wrong_password = 'incorrect_password' + + it 'increments failed_attempts when true and password is incorrect' do + expect do + gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true) + user.reload + end.to change(user, :failed_attempts).from(0).to(1) + end + + it 'resets failed_attempts when true and password is correct' do + user.failed_attempts = 2 + user.save + + expect do + gl_auth.find_with_user_password(username, password, increment_failed_attempts: true) + user.reload + end.to change(user, :failed_attempts).from(2).to(0) + end + + it 'does not increment failed_attempts by default' do + expect do + gl_auth.find_with_user_password(username, wrong_password) + user.reload + end.not_to change(user, :failed_attempts) + end + + context 'when the database is read only' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end + + it 'does not increment failed_attempts when true and password is incorrect' do + expect do + gl_auth.find_with_user_password(username, wrong_password, increment_failed_attempts: true) + user.reload + end.not_to change(user, :failed_attempts) + end + + it 'does not reset failed_attempts when true and password is correct' do + user.failed_attempts = 2 + user.save + + expect do + gl_auth.find_with_user_password(username, password, increment_failed_attempts: true) + user.reload + end.not_to change(user, :failed_attempts) + end + end + end + context "with ldap enabled" do before do allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true) diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 491437856d4..8961cdcae7d 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -192,7 +192,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do end describe '.find with Gitaly enabled' do - it_should_behave_like '.find' + it_behaves_like '.find' end describe '.find with Rugged enabled', :enable_rugged do @@ -204,7 +204,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do described_class.find(repository, SeedRepo::Commit::ID) end - it_should_behave_like '.find' + it_behaves_like '.find' end describe '.last_for_path' do @@ -474,7 +474,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do end describe '.batch_by_oid with Gitaly enabled' do - it_should_behave_like '.batch_by_oid' + it_behaves_like '.batch_by_oid' context 'when oids is empty' do it 'makes no Gitaly request' do @@ -486,7 +486,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do end describe '.batch_by_oid with Rugged enabled', :enable_rugged do - it_should_behave_like '.batch_by_oid' + it_behaves_like '.batch_by_oid' it 'calls out to the Rugged implementation' do allow_next_instance_of(Rugged) do |instance| diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 8153886a2ab..85567ab2e55 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -472,31 +472,135 @@ RSpec.describe Gitlab::GitAccess do let(:actor) { key } context 'pull code' do - context 'when project is authorized' do - before do - key.projects << project + context 'when project is public' do + let(:project) { create(:project, :public, :repository, *options) } + + context 'when deploy key exists in the project' do + before do + key.projects << project + end + + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end end - it { expect { pull_access_check }.not_to raise_error } + context 'when deploy key does not exist in the project' do + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end + end end - context 'when unauthorized' do - context 'from public project' do - let(:project) { create(:project, :public, :repository) } + context 'when project is internal' do + let(:project) { create(:project, :internal, :repository, *options) } - it { expect { pull_access_check }.not_to raise_error } + context 'when deploy key exists in the project' do + before do + key.projects << project + end + + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end end - context 'from internal project' do - let(:project) { create(:project, :internal, :repository) } + context 'when deploy key does not exist in the project' do + context 'when the repository is public' do + let(:options) { %i[repository_enabled] } - it { expect { pull_access_check }.to raise_not_found } + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end end + end - context 'from private project' do - let(:project) { create(:project, :private, :repository) } + context 'when project is private' do + let(:project) { create(:project, :private, :repository, *options) } - it { expect { pull_access_check }.to raise_not_found } + context 'when deploy key exists in the project' do + before do + key.projects << project + end + + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.not_to raise_error } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('You are not allowed to download code from this project.') } + end + end + + context 'when deploy key does not exist in the project' do + context 'when the repository is private' do + let(:options) { %i[repository_private] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end + + context 'when the repository is disabled' do + let(:options) { %i[repository_disabled] } + + it { expect { pull_access_check }.to raise_error('The project you were looking for could not be found.') } + end end end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index f736f19ca9a..88c3315150b 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -195,15 +195,6 @@ RSpec.describe Gitlab::Regex do it { is_expected.not_to match('foo/bar') } end - describe '.conan_file_name_regex' do - subject { described_class.conan_file_name_regex } - - it { is_expected.to match('conanfile.py') } - it { is_expected.to match('conan_package.tgz') } - it { is_expected.not_to match('foo.txt') } - it { is_expected.not_to match('!!()()') } - end - describe '.conan_package_reference_regex' do subject { described_class.conan_package_reference_regex } diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index cac2c410132..e9733851590 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -71,7 +71,7 @@ RSpec.describe Gitlab::Workhorse do context "when the repository doesn't have an archive file path" do before do - allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) + allow(project.repository).to receive(:archive_metadata).and_return({}) end it "raises an error" do diff --git a/spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb b/spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb new file mode 100644 index 00000000000..e12519e15b8 --- /dev/null +++ b/spec/migrations/20200728182311_add_o_auth_paths_to_protected_paths_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20200728182311_add_o_auth_paths_to_protected_paths.rb') + +RSpec.describe AddOAuthPathsToProtectedPaths do + subject(:migration) { described_class.new } + + let(:application_settings) { table(:application_settings) } + let(:new_paths) do + [ + '/oauth/authorize', + '/oauth/token' + ] + end + + it 'appends new OAuth paths' do + application_settings.create! + + protected_paths_before = application_settings.first.protected_paths + protected_paths_after = protected_paths_before + new_paths + + expect { migrate! }.to change { application_settings.first.protected_paths }.from(protected_paths_before).to(protected_paths_after) + end + + it 'new default includes new paths' do + settings_before = application_settings.create! + + expect(settings_before.protected_paths).not_to include(*new_paths) + + migrate! + + application_settings.reset_column_information + settings_after = application_settings.create! + + expect(settings_after.protected_paths).to include(*new_paths) + end + + it 'does not change the value when the new paths are already included' do + application_settings.create!(protected_paths: %w(/users/sign_in /users/password) + new_paths) + + expect { migrate! }.not_to change { application_settings.first.protected_paths } + end + + it 'adds one value when the other is already present' do + application_settings.create!(protected_paths: %W(/users/sign_in /users/password #{new_paths.first})) + + migrate! + + expect(application_settings.first.protected_paths).to include(new_paths.second) + end +end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 24b47be3c69..de39c8c7c5c 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -296,6 +296,59 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do end end + describe '.destroy_all_but_current' do + it 'gracefully handles a nil session ID' do + expect(described_class).not_to receive(:destroy_sessions) + + ActiveSession.destroy_all_but_current(user, nil) + end + + context 'with user sessions' do + let(:current_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' } + + before do + Gitlab::Redis::SharedState.with do |redis| + redis.set(described_class.key_name(user.id, current_session_id), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(current_session_id)))) + redis.set(described_class.key_name(user.id, '59822c7d9fcdfa03725eff41782ad97d'), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('59822c7d9fcdfa03725eff41782ad97d')))) + redis.set(described_class.key_name(9999, '5c8611e4f9c69645ad1a1492f4131358'), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new('5c8611e4f9c69645ad1a1492f4131358')))) + redis.sadd(described_class.lookup_key_name(user.id), '59822c7d9fcdfa03725eff41782ad97d') + redis.sadd(described_class.lookup_key_name(user.id), current_session_id) + redis.sadd(described_class.lookup_key_name(9999), '5c8611e4f9c69645ad1a1492f4131358') + end + end + + it 'removes the entry associated with the all user sessions but current' do + expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(2).to(1) + + expect(ActiveSession.session_ids_for_user(9999).size).to eq(1) + end + + it 'removes the lookup entry of deleted sessions' do + ActiveSession.destroy_all_but_current(user, request.session) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.smembers(described_class.lookup_key_name(user.id))).to eq [current_session_id] + end + end + + it 'does not remove impersonated sessions' do + impersonated_session_id = '6919a6f1bb119dd7396fadc38fd18eee' + Gitlab::Redis::SharedState.with do |redis| + redis.set(described_class.key_name(user.id, impersonated_session_id), + Marshal.dump(ActiveSession.new(session_id: Rack::Session::SessionId.new(impersonated_session_id), is_impersonated: true))) + redis.sadd(described_class.lookup_key_name(user.id), impersonated_session_id) + end + + expect { ActiveSession.destroy_all_but_current(user, request.session) }.to change { ActiveSession.session_ids_for_user(user.id).size }.from(3).to(2) + + expect(ActiveSession.session_ids_for_user(9999).size).to eq(1) + end + end + end + describe '.cleanup' do before do stub_const("ActiveSession::ALLOWED_NUMBER_OF_ACTIVE_SESSIONS", 5) diff --git a/spec/models/aws/role_spec.rb b/spec/models/aws/role_spec.rb index 612868f6eb0..ee93c9d6fad 100644 --- a/spec/models/aws/role_spec.rb +++ b/spec/models/aws/role_spec.rb @@ -29,6 +29,12 @@ RSpec.describe Aws::Role do it { is_expected.to be_truthy } end + + context 'ARN is nil' do + let(:role_arn) { } + + it { is_expected.to be_truthy } + end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 8da164e041c..b83c769284a 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -195,6 +195,19 @@ RSpec.describe Member do it { expect(described_class.non_request).to include @accepted_request_member } end + describe '.not_accepted_invitations_by_user' do + let(:invited_by_user) { create(:project_member, :invited, project: project, created_by: @owner_user) } + + before do + create(:project_member, :invited, invite_email: 'test@test.com', project: project, created_by: @owner_user, invite_accepted_at: Time.zone.now) + create(:project_member, :invited, invite_email: 'test2@test.com', project: project, created_by: @maintainer_user) + end + + subject { described_class.not_accepted_invitations_by_user(@owner_user) } + + it { is_expected.to contain_exactly(invited_by_user) } + end + describe '.search_invite_email' do it 'returns only members the matching e-mail' do create(:group_member, :invited) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 98f1c5bb2cc..74a36daa727 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -895,6 +895,20 @@ RSpec.describe User do expect(described_class.without_ghosts).to match_array([user1, user2]) end end + + describe '.by_id_and_login' do + let_it_be(:user) { create(:user) } + + it 'finds a user regardless of case' do + expect(described_class.by_id_and_login(user.id, user.username.upcase)) + .to contain_exactly(user) + end + + it 'finds a user when login is an email address regardless of case' do + expect(described_class.by_id_and_login(user.id, user.email.upcase)) + .to contain_exactly(user) + end + end end describe "Respond to" do @@ -3580,6 +3594,42 @@ RSpec.describe User do end end + describe '#source_groups_of_two_factor_authentication_requirement' do + let_it_be(:group_not_requiring_2FA) { create :group } + let(:user) { create :user } + + before do + group.add_user(user, GroupMember::OWNER) + group_not_requiring_2FA.add_user(user, GroupMember::OWNER) + end + + context 'when user is direct member of group requiring 2FA' do + let_it_be(:group) { create :group, require_two_factor_authentication: true } + + it 'returns group requiring 2FA' do + expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group) + end + end + + context 'when user is member of group which parent requires 2FA' do + let_it_be(:parent_group) { create :group, require_two_factor_authentication: true } + let_it_be(:group) { create :group, parent: parent_group } + + it 'returns group requiring 2FA' do + expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group) + end + end + + context 'when user is member of group which child requires 2FA' do + let_it_be(:group) { create :group } + let_it_be(:child_group) { create :group, require_two_factor_authentication: true, parent: group } + + it 'returns group requiring 2FA' do + expect(user.source_groups_of_two_factor_authentication_requirement).to contain_exactly(group) + end + end + end + describe '.active' do before do described_class.ghost diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb index 99d224cb8e9..d8a345a79b0 100644 --- a/spec/requests/api/badges_spec.rb +++ b/spec/requests/api/badges_spec.rb @@ -332,10 +332,32 @@ RSpec.describe API::Badges do context 'when deleting a badge' do context 'and the source is a project' do + let(:badge) { project.group.badges.first } + it 'cannot delete badges owned by the project group' do - delete api("/projects/#{project.id}/badges/#{project_group.badges.first.id}", maintainer) + expect do + delete api("/projects/#{project.id}/badges/#{badge.id}", maintainer) + + expect(response).to have_gitlab_http_status(:not_found) + end.not_to change { badge.reload.persisted? } + end + end + end + + context 'when updating a badge' do + context 'and the source is a project' do + let(:badge) { project.group.badges.first } + let(:example_name) { 'BadgeName' } + let(:example_url) { 'http://www.example.com' } + let(:example_url2) { 'http://www.example1.com' } + + it 'cannot update badges owned by the project group' do + expect do + put api("/projects/#{project.id}/badges/#{badge.id}", maintainer), + params: { name: example_name, link_url: example_url, image_url: example_url2 } - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:not_found) + end.not_to change { badge.reload.updated_at } end end end diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb index 58c360d055b..d1ddaa277fc 100644 --- a/spec/requests/api/conan_packages_spec.rb +++ b/spec/requests/api/conan_packages_spec.rb @@ -14,7 +14,7 @@ RSpec.describe API::ConanPackages do let(:base_secret) { SecureRandom.base64(64) } let(:auth_token) { personal_access_token.token } - let(:job) { create(:ci_build, user: user) } + let(:job) { create(:ci_build, user: user, status: :running) } let(:job_token) { job.token } let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } @@ -94,6 +94,14 @@ RSpec.describe API::ConanPackages do expect(response).to have_gitlab_http_status(:unauthorized) end + it 'responds with 401 Unauthorized when the job is not running' do + job.update!(status: :failed) + jwt = build_jwt_from_job(job) + get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + context 'packages feature disabled' do it 'responds with 404 Not Found' do stub_packages_setting(enabled: false) @@ -234,6 +242,18 @@ RSpec.describe API::ConanPackages do end end + shared_examples 'rejects invalid file_name' do |invalid_file_name| + let(:file_name) { invalid_file_name } + + context 'with invalid file_name' do + it 'returns 400' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + shared_examples 'rejects recipe for invalid project' do context 'with invalid recipe path' do let(:recipe_path) { 'aa/bb/not-existing-project/ccc' } @@ -717,8 +737,6 @@ RSpec.describe API::ConanPackages do context 'without a file from workhorse' do let(:params) { { file: nil } } - it_behaves_like 'package workhorse uploads' - it 'rejects the request' do subject @@ -726,6 +744,10 @@ RSpec.describe API::ConanPackages do end end + context 'with a file' do + it_behaves_like 'package workhorse uploads' + end + context 'without a token' do it 'rejects request without a token' do headers_with_token.delete('HTTP_AUTHORIZATION') @@ -884,16 +906,22 @@ RSpec.describe API::ConanPackages do end describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do - subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token } + let(:file_name) { 'conanfile.py' } + + subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}/authorize"), headers: headers_with_token } it_behaves_like 'rejects invalid recipe' + it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack' it_behaves_like 'workhorse authorization' end describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do - subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token } + let(:file_name) { 'conaninfo.txt' } + + subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}/authorize"), headers: headers_with_token } it_behaves_like 'rejects invalid recipe' + it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest' it_behaves_like 'workhorse authorization' end @@ -907,11 +935,13 @@ RSpec.describe API::ConanPackages do method: :put, file_key: :file, params: params, + send_rewritten_field: true, headers: headers_with_token ) end it_behaves_like 'rejects invalid recipe' + it_behaves_like 'rejects invalid file_name', 'conanfile.py.git%2fgit-upload-pack' it_behaves_like 'uploads a package file' end @@ -925,12 +955,15 @@ RSpec.describe API::ConanPackages do method: :put, file_key: :file, params: params, - headers: headers_with_token + headers: headers_with_token, + send_rewritten_field: true ) end it_behaves_like 'rejects invalid recipe' + it_behaves_like 'rejects invalid file_name', 'conaninfo.txttest' it_behaves_like 'uploads a package file' + context 'tracking the conan_package.tgz upload' do let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY } diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb index e08637629cc..ed852fe75c7 100644 --- a/spec/requests/api/generic_packages_spec.rb +++ b/spec/requests/api/generic_packages_spec.rb @@ -55,7 +55,7 @@ RSpec.describe API::GenericPackages do context 'authenticating using job token' do it 'responds with 200 OK when valid job token is provided' do - job_token = create(:ci_build, user: user).token + job_token = create(:ci_build, :running, user: user).token ping(job_token: job_token) diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb index 2d7e319b0be..9d422ebbce2 100644 --- a/spec/requests/api/go_proxy_spec.rb +++ b/spec/requests/api/go_proxy_spec.rb @@ -11,7 +11,7 @@ RSpec.describe API::GoProxy do let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" } let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user } - let_it_be(:job) { create :ci_build, user: user } + let_it_be(:job) { create :ci_build, user: user, status: :running } let_it_be(:pa_token) { create :personal_access_token, user: user } let_it_be(:modules) do @@ -393,6 +393,13 @@ RSpec.describe API::GoProxy do expect(response).to have_gitlab_http_status(:ok) end + it 'returns unauthorized with a failed job token' do + job.update!(status: :failed) + get_resource(oauth_access_token: job) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + it 'returns unauthorized with no authentication' do get_resource diff --git a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb new file mode 100644 index 00000000000..328f4fb7b6e --- /dev/null +++ b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Create a label or backlog board list' do + include GraphqlHelpers + + let_it_be(:group) { create(:group, :private) } + let_it_be(:board) { create(:board, group: group) } + let_it_be(:user) { create(:user) } + let_it_be(:dev_label) do + create(:group_label, title: 'Development', color: '#FFAABB', group: group) + end + + let(:current_user) { user } + let(:mutation) { graphql_mutation(:board_list_create, input) } + let(:mutation_response) { graphql_mutation_response(:board_list_create) } + + context 'the user is not allowed to read board lists' do + let(:input) { { board_id: board.to_global_id.to_s, backlog: true } } + + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user has permissions to admin board lists' do + before do + group.add_reporter(current_user) + end + + describe 'backlog list' do + let(:input) { { board_id: board.to_global_id.to_s, backlog: true } } + + it 'creates the list' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['list']) + .to include('position' => nil, 'listType' => 'backlog') + end + end + + describe 'label list' do + let(:input) { { board_id: board.to_global_id.to_s, label_id: dev_label.to_global_id.to_s } } + + it 'creates the list' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['list']) + .to include('position' => 0, 'listType' => 'label', 'label' => include('title' => 'Development')) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb index 8ade72635af..b71f87d2702 100644 --- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb @@ -46,6 +46,31 @@ RSpec.describe 'Destroying a Snippet' do expect(mutation_response).to have_key('snippet') expect(mutation_response['snippet']).to be_nil end + + context 'when a bad gid is given' do + let!(:project) { create(:project, :private) } + let!(:snippet) { create(:project_snippet, :private, project: project, author: create(:user)) } + let!(:snippet_gid) { project.to_gid.to_s } + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors) + .to include(a_hash_including('message' => "#{snippet_gid} is not a valid ID for Snippet.")) + end + + it 'does not destroy the Snippet' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Snippet.count } + end + + it 'does not destroy the Project' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Project.count } + end + end end end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 090ff3c1b5a..9c0ea14e3e3 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -85,6 +85,27 @@ RSpec.describe API::Helpers do end it { is_expected.to eq(user) } + + context 'when user should have 2fa enabled' do + before do + allow(user).to receive(:require_two_factor_authentication_from_group?).and_return(true) + allow_next_instance_of(Gitlab::Auth::TwoFactorAuthVerifier) do |verifier| + allow(verifier).to receive(:two_factor_grace_period_expired?).and_return(true) + end + end + + context 'when 2fa is not enabled' do + it { is_expected.to be_nil } + end + + context 'when 2fa is enabled' do + before do + allow(user).to receive(:two_factor_enabled?).and_return(true) + end + + it { is_expected.to eq(user) } + end + end end context "PUT request" do diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 6555a055678..f90e04bbd2e 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -56,7 +56,7 @@ RSpec.describe API::Jobs do end describe 'GET /projects/:id/jobs' do - let(:query) { Hash.new } + let(:query) { {} } before do |example| unless example.metadata[:skip_before_request] @@ -167,7 +167,7 @@ RSpec.describe API::Jobs do end describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do - let(:query) { Hash.new } + let(:query) { {} } before do |example| unless example.metadata[:skip_before_request] @@ -296,7 +296,7 @@ RSpec.describe API::Jobs do project: downstream_pipeline.project) end - let(:query) { Hash.new } + let(:query) { {} } before do |example| unless example.metadata[:skip_before_request] diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index 99588694bab..04d1d2cf1e8 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -12,7 +12,7 @@ RSpec.describe API::MavenPackages do let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first } let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let_it_be(:job) { create(:ci_build, user: user) } + let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } @@ -102,11 +102,25 @@ RSpec.describe API::MavenPackages do end shared_examples 'downloads with a job token' do - it 'allows download with job token' do - download_file(package_file.file_name, job_token: job.token) + context 'with a running job' do + it 'allows download with job token' do + download_file(package_file.file_name, job_token: job.token) - expect(response).to have_gitlab_http_status(:ok) - expect(response.media_type).to eq('application/octet-stream') + expect(response).to have_gitlab_http_status(:ok) + expect(response.media_type).to eq('application/octet-stream') + end + end + + context 'with a finished job' do + before do + job.update!(status: :failed) + end + + it 'returns unauthorized error' do + download_file(package_file.file_name, job_token: job.token) + + expect(response).to have_gitlab_http_status(:unauthorized) + end end end @@ -569,13 +583,20 @@ RSpec.describe API::MavenPackages do expect(jar_file.file_name).to eq(file_upload.original_filename) end - it 'allows upload with job token' do + it 'allows upload with running job token' do upload_file(params.merge(job_token: job.token)) expect(response).to have_gitlab_http_status(:ok) expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline end + it 'rejects upload without running job token' do + job.update!(status: :failed) + upload_file(params.merge(job_token: job.token)) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + it 'allows upload with deploy token' do upload_file(params, headers_with_deploy_token) diff --git a/spec/requests/api/npm_packages_spec.rb b/spec/requests/api/npm_packages_spec.rb index 94647123df0..108ea84b7e6 100644 --- a/spec/requests/api/npm_packages_spec.rb +++ b/spec/requests/api/npm_packages_spec.rb @@ -12,7 +12,7 @@ RSpec.describe API::NpmPackages do let_it_be(:package, reload: true) { create(:npm_package, project: project) } let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let_it_be(:job) { create(:ci_build, user: user) } + let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } @@ -27,12 +27,19 @@ RSpec.describe API::NpmPackages do expect_a_valid_package_response end - it 'returns the package info with job token' do + it 'returns the package info with running job token' do get_package_with_job_token(package) expect_a_valid_package_response end + it 'denies request without running job token' do + job.update!(status: :success) + get_package_with_job_token(package) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + it 'denies request without oauth token' do get_package(package) diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb index 37170592b60..62f244c433b 100644 --- a/spec/requests/api/nuget_packages_spec.rb +++ b/spec/requests/api/nuget_packages_spec.rb @@ -80,7 +80,7 @@ RSpec.describe API::NugetPackages do end with_them do - let(:job) { user_token ? create(:ci_build, project: project, user: user) : double(token: 'wrong') } + let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') } let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) } subject { get api(url), headers: headers } diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index a9a92a4d3cd..779ae983886 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -671,12 +671,20 @@ RSpec.describe API::Releases do end context 'when a valid token is provided' do - it 'creates the release' do + it 'creates the release for a running job' do + job.update!(status: :running) post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token) expect(response).to have_gitlab_http_status(:created) expect(project.releases.last.description).to eq('Another nice release') end + + it 'returns an :unauthorized error for a completed job' do + job.success! + post api("/projects/#{project.id}/releases"), params: params.merge(job_token: job.token) + + expect(response).to have_gitlab_http_status(:unauthorized) + end end end diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb index c6cba39314b..c47a12456c3 100644 --- a/spec/requests/api/terraform/state_spec.rb +++ b/spec/requests/api/terraform/state_spec.rb @@ -72,7 +72,7 @@ RSpec.describe API::Terraform::State do let(:auth_header) { job_basic_auth_header(job) } context 'with maintainer permissions' do - let(:job) { create(:ci_build, project: project, user: maintainer) } + let(:job) { create(:ci_build, status: :running, project: project, user: maintainer) } it 'returns terraform state belonging to a project of given state name' do request @@ -81,6 +81,13 @@ RSpec.describe API::Terraform::State do expect(response.body).to eq(state.file.read) end + it 'returns unauthorized if the the job is not running' do + job.update!(status: :failed) + request + + expect(response).to have_gitlab_http_status(:unauthorized) + end + context 'for a project that does not exist' do let(:project_id) { '0000' } @@ -93,7 +100,7 @@ RSpec.describe API::Terraform::State do end context 'with developer permissions' do - let(:job) { create(:ci_build, project: project, user: developer) } + let(:job) { create(:ci_build, status: :running, project: project, user: developer) } it 'returns terraform state belonging to a project of given state name' do request diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb index 58ac723ee55..8b8beb057a9 100644 --- a/spec/services/applications/create_service_spec.rb +++ b/spec/services/applications/create_service_spec.rb @@ -6,9 +6,24 @@ RSpec.describe ::Applications::CreateService do include TestRequestHelpers let(:user) { create(:user) } - let(:params) { attributes_for(:application) } subject { described_class.new(user, params) } - it { expect { subject.execute(test_request) }.to change { Doorkeeper::Application.count }.by(1) } + context 'when scopes are present' do + let(:params) { attributes_for(:application, scopes: ['read_user']) } + + it { expect { subject.execute(test_request) }.to change { Doorkeeper::Application.count }.by(1) } + end + + context 'when scopes are missing' do + let(:params) { attributes_for(:application) } + + it { expect { subject.execute(test_request) }.not_to change { Doorkeeper::Application.count } } + + it 'includes blank scopes error message' do + application = subject.execute(test_request) + + expect(application.errors.full_messages).to include "Scopes can't be blank" + end + end end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 8d58c4b27e1..bc85f4f0087 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -654,6 +654,19 @@ RSpec.describe Auth::ContainerRegistryAuthenticationService do it_behaves_like 'not a container repository factory' end end + + context 'for project that disables repository' do + let(:project) { create(:project, :public, :repository_disabled) } + + context 'disallow when pulling' do + let(:current_params) do + { scopes: ["repository:#{project.full_path}:pull"] } + end + + it_behaves_like 'an inaccessible' + it_behaves_like 'not a container repository factory' + end + end end context 'registry catalog browsing authorized as admin' do diff --git a/spec/services/branches/delete_service_spec.rb b/spec/services/branches/delete_service_spec.rb index f1e7c9340b1..291431c1723 100644 --- a/spec/services/branches/delete_service_spec.rb +++ b/spec/services/branches/delete_service_spec.rb @@ -37,6 +37,21 @@ RSpec.describe Branches::DeleteService do end it_behaves_like 'a deleted branch', 'feature' + + context 'when Gitlab::Git::CommandError is raised' do + before do + allow(repository).to receive(:rm_branch) do + raise Gitlab::Git::CommandError.new('Could not update patch') + end + end + + it 'handles and returns error' do + result = service.execute('feature') + + expect(result.status).to eq(:error) + expect(result.message).to eq('Could not update patch') + end + end end context 'when user does not have access to push to repository' do diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb index 18fab9623ec..ac077e3c30e 100644 --- a/spec/services/ci/pipeline_trigger_service_spec.rb +++ b/spec/services/ci/pipeline_trigger_service_spec.rb @@ -106,9 +106,23 @@ RSpec.describe Ci::PipelineTriggerService do let(:params) { { token: job.token, ref: 'master', variables: nil } } let(:job) { create(:ci_build, :success, pipeline: pipeline, user: user) } - it 'does nothing' do + it 'does nothing', :aggregate_failures do + expect { result }.not_to change { Ci::Pipeline.count } + expect(result[:message]).to eq('Job is not running') + expect(result[:http_status]).to eq(401) + end + end + + context 'when job does not have a project' do + let(:params) { { token: job.token, ref: 'master', variables: nil } } + let(:job) { create(:ci_build, status: :running, pipeline: pipeline, user: user) } + + it 'does nothing', :aggregate_failures do + job.update!(project: nil) + expect { result }.not_to change { Ci::Pipeline.count } - expect(result[:message]).to eq('400 Job has to be running') + expect(result[:message]).to eq('Project has been deleted!') + expect(result[:http_status]).to eq(401) end end diff --git a/spec/services/clusters/aws/authorize_role_service_spec.rb b/spec/services/clusters/aws/authorize_role_service_spec.rb index 3d12400a47b..5b47cf0ecde 100644 --- a/spec/services/clusters/aws/authorize_role_service_spec.rb +++ b/spec/services/clusters/aws/authorize_role_service_spec.rb @@ -3,47 +3,34 @@ require 'spec_helper' RSpec.describe Clusters::Aws::AuthorizeRoleService do - let(:user) { create(:user) } + subject { described_class.new(user, params: params).execute } + + let(:role) { create(:aws_role) } + let(:user) { role.user } let(:credentials) { instance_double(Aws::Credentials) } let(:credentials_service) { instance_double(Clusters::Aws::FetchCredentialsService, execute: credentials) } + let(:role_arn) { 'arn:my-role' } let(:params) do params = ActionController::Parameters.new({ cluster: { - role_arn: 'arn:my-role', - role_external_id: 'external-id' + role_arn: role_arn } }) - params.require(:cluster).permit(:role_arn, :role_external_id) + params.require(:cluster).permit(:role_arn) end - subject { described_class.new(user, params: params).execute } - before do allow(Clusters::Aws::FetchCredentialsService).to receive(:new) .with(instance_of(Aws::Role)).and_return(credentials_service) end - context 'role does not exist' do - it 'creates an Aws::Role record and returns a set of credentials' do - expect(user).to receive(:create_aws_role!) - .with(params).and_call_original - - expect(subject.status).to eq(:ok) - expect(subject.body).to eq(credentials) - end - end - - context 'role already exists' do - let(:role) { create(:aws_role, user: user) } - + context 'role exists' do it 'updates the existing Aws::Role record and returns a set of credentials' do - expect(role).to receive(:update!) - .with(params).and_call_original - expect(subject.status).to eq(:ok) expect(subject.body).to eq(credentials) + expect(role.reload.role_arn).to eq(role_arn) end end @@ -61,11 +48,14 @@ RSpec.describe Clusters::Aws::AuthorizeRoleService do end end - context 'cannot create role' do - before do - allow(user).to receive(:create_aws_role!) - .and_raise(ActiveRecord::RecordInvalid.new(user)) - end + context 'role does not exist' do + let(:user) { create(:user) } + + include_examples 'bad request' + end + + context 'supplied ARN is invalid' do + let(:role_arn) { 'invalid' } include_examples 'bad request' end diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 13e7b4c1006..5c90f1f54ea 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -292,6 +292,10 @@ RSpec.describe Members::DestroyService do before do create(:group_member, :developer, group: subsubgroup, user: member_user) + create(:project_member, :invited, project: group_project, created_by: member_user) + create(:group_member, :invited, group: group, created_by: member_user) + create(:project_member, :invited, project: subsubproject, created_by: member_user) + create(:group_member, :invited, group: subgroup, created_by: member_user) subsubproject.add_developer(member_user) control_project.add_maintainer(user) @@ -325,5 +329,41 @@ RSpec.describe Members::DestroyService do it 'does not remove the user from the control project' do expect(control_project.members.map(&:user)).to include(user) end + + it 'removes group members invited by deleted user' do + expect(group.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + + it 'removes project members invited by deleted user' do + expect(group_project.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + + it 'removes subgroup members invited by deleted user' do + expect(subgroup.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + + it 'removes subproject members invited by deleted user' do + expect(subsubproject.members.not_accepted_invitations_by_user(member_user)).to be_empty + end + end + + context 'deletion of invitations created by deleted project member' do + let(:user) { project.owner } + let(:member_user) { create(:user) } + let(:opts) { {} } + + let(:project) { create(:project) } + + before do + create(:project_member, :invited, project: project, created_by: member_user) + + project_member = create(:project_member, :maintainer, user: member_user, project: project) + + described_class.new(user).execute(project_member, opts) + end + + it 'removes project members invited by deleted user' do + expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty + end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 47bc6bcd989..87d6f99e8aa 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -151,9 +151,9 @@ RSpec.describe NotificationService, :mailer do end shared_examples_for 'participating notifications' do - it_should_behave_like 'participating by note notification' - it_should_behave_like 'participating by author notification' - it_should_behave_like 'participating by assignee notification' + it_behaves_like 'participating by note notification' + it_behaves_like 'participating by author notification' + it_behaves_like 'participating by assignee notification' end describe '#async' do @@ -1682,13 +1682,13 @@ RSpec.describe NotificationService, :mailer do end context 'participating' do - it_should_behave_like 'participating by assignee notification' do + it_behaves_like 'participating by assignee notification' do let(:participant) { create(:user, username: 'user-participant')} let(:issuable) { merge_request } let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end - it_should_behave_like 'participating by note notification' do + it_behaves_like 'participating by note notification' do let(:participant) { create(:user, username: 'user-participant')} let(:issuable) { merge_request } let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index 09244db8010..a452f88c75f 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -56,6 +56,39 @@ RSpec.describe Projects::UpdateRemoteMirrorService do expect(remote_mirror.last_error).to include('Badly broken') end + context 'when the URL is blocked' do + before do + allow(Gitlab::UrlBlocker).to receive(:blocked_url?).and_return(true) + end + + it 'fails and returns error status' do + expect(execute!).to eq(status: :error, message: 'The remote mirror URL is invalid.') + end + end + + context "when given URLs containing escaped elements" do + using RSpec::Parameterized::TableSyntax + + where(:url, :result_status) do + "https://user:0a%23@test.example.com/project.git" | :success + "https://git.example.com:1%2F%2F@source.developers.google.com/project.git" | :success + CGI.escape("git://localhost:1234/some-path?some-query=some-val\#@example.com/") | :error + CGI.escape(CGI.escape("https://user:0a%23@test.example.com/project.git")) | :error + end + + with_them do + before do + allow(remote_mirror).to receive(:url).and_return(url) + end + + it "returns expected status" do + result = execute! + + expect(result[:status]).to eq(result_status) + end + end + end + context 'when the update fails because of a `Gitlab::Git::CommandError`' do before do allow(remote_mirror).to receive(:update_repository) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 395930c8cbc..06ec97d65b2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -47,10 +47,10 @@ require_relative('../ee/spec/spec_helper') if Gitlab.ee? require Rails.root.join("spec/support/helpers/git_helpers.rb") # Then the rest -Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f } -Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } -Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } -Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } +Dir[Rails.root.join("spec/support/helpers/*.rb")].sort.each { |f| require f } +Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].sort.each { |f| require f } +Dir[Rails.root.join("spec/support/shared_examples/*.rb")].sort.each { |f| require f } +Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f } quality_level = Quality::TestLevel.new diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb index 0486eccf48c..476b7d34ee5 100644 --- a/spec/support/helpers/stub_object_storage.rb +++ b/spec/support/helpers/stub_object_storage.rb @@ -21,7 +21,7 @@ module StubObjectStorage proxy_download: false, background_upload: false, direct_upload: false - ) + ) allow(config).to receive(:enabled) { enabled } allow(config).to receive(:proxy_download) { proxy_download } allow(config).to receive(:background_upload) { background_upload } diff --git a/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb b/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb new file mode 100644 index 00000000000..aa17e72d08e --- /dev/null +++ b/spec/support/shared_examples/controllers/clusters_controller_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'GET new cluster shared examples' do + describe 'EKS cluster' do + context 'user already has an associated AWS role' do + let!(:role) { create(:aws_role, user: user) } + + it 'does not create an Aws::Role record' do + expect { go(provider: 'aws') }.not_to change { Aws::Role.count } + + expect(response).to have_gitlab_http_status(:ok) + expect(assigns(:aws_role)).to eq(role) + end + end + + context 'user does not have an associated AWS role' do + it 'creates an Aws::Role record' do + expect { go(provider: 'aws') }.to change { Aws::Role.count } + + expect(response).to have_gitlab_http_status(:ok) + + role = assigns(:aws_role) + expect(role.user).to eq(user) + expect(role.role_arn).to be_nil + expect(role.role_external_id).to be_present + end + end + end +end |