summaryrefslogtreecommitdiff
path: root/spec/controllers
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
commit48aff82709769b098321c738f3444b9bdaa694c6 (patch)
treee00c7c43e2d9b603a5a6af576b1685e400410dee /spec/controllers
parent879f5329ee916a948223f8f43d77fba4da6cd028 (diff)
downloadgitlab-ce-48aff82709769b098321c738f3444b9bdaa694c6.tar.gz
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'spec/controllers')
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb38
-rw-r--r--spec/controllers/admin/clusters_controller_spec.rb5
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb8
-rw-r--r--spec/controllers/admin/instance_review_controller_spec.rb68
-rw-r--r--spec/controllers/admin/integrations_controller_spec.rb14
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb17
-rw-r--r--spec/controllers/admin/sessions_controller_spec.rb2
-rw-r--r--spec/controllers/admin/users_controller_spec.rb90
-rw-r--r--spec/controllers/application_controller_spec.rb12
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb11
-rw-r--r--spec/controllers/concerns/controller_with_feature_category/config_spec.rb53
-rw-r--r--spec/controllers/concerns/controller_with_feature_category_spec.rb38
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb34
-rw-r--r--spec/controllers/concerns/redis_tracking_spec.rb99
-rw-r--r--spec/controllers/dashboard/labels_controller_spec.rb23
-rw-r--r--spec/controllers/dashboard_controller_spec.rb10
-rw-r--r--spec/controllers/every_controller_spec.rb20
-rw-r--r--spec/controllers/graphql_controller_spec.rb10
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb5
-rw-r--r--spec/controllers/groups/group_links_controller_spec.rb54
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb38
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb79
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb7
-rw-r--r--spec/controllers/groups/registry/repositories_controller_spec.rb2
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb21
-rw-r--r--spec/controllers/groups_controller_spec.rb98
-rw-r--r--spec/controllers/help_controller_spec.rb63
-rw-r--r--spec/controllers/import/bulk_imports_controller_spec.rb179
-rw-r--r--spec/controllers/import/manifest_controller_spec.rb99
-rw-r--r--spec/controllers/invites_controller_spec.rb89
-rw-r--r--spec/controllers/jira_connect/events_controller_spec.rb37
-rw-r--r--spec/controllers/profiles_controller_spec.rb13
-rw-r--r--spec/controllers/projects/alert_management_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb4
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb6
-rw-r--r--spec/controllers/projects/feature_flags_clients_controller_spec.rb57
-rw-r--r--spec/controllers/projects/feature_flags_controller_spec.rb1604
-rw-r--r--spec/controllers/projects/feature_flags_user_lists_controller_spec.rb113
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb51
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb8
-rw-r--r--spec/controllers/projects/import/jira_controller_spec.rb2
-rw-r--r--spec/controllers/projects/incidents_controller_spec.rb103
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb18
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb94
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb95
-rw-r--r--spec/controllers/projects/merge_requests/conflicts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb4
-rw-r--r--spec/controllers/projects/pipelines/stages_controller_spec.rb11
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb80
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb52
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb2
-rw-r--r--spec/controllers/projects/releases/evidences_controller_spec.rb32
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb8
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb41
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb16
-rw-r--r--spec/controllers/projects/settings/access_tokens_controller_spec.rb171
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb32
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb115
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb283
-rw-r--r--spec/controllers/projects/static_site_editor_controller_spec.rb92
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb21
-rw-r--r--spec/controllers/projects/tracings_controller_spec.rb62
-rw-r--r--spec/controllers/projects/web_ide_terminals_controller_spec.rb23
-rw-r--r--spec/controllers/projects_controller_spec.rb4
-rw-r--r--spec/controllers/registrations/experience_levels_controller_spec.rb49
-rw-r--r--spec/controllers/registrations_controller_spec.rb282
-rw-r--r--spec/controllers/runner_setup_controller_spec.rb21
-rw-r--r--spec/controllers/search_controller_spec.rb2
-rw-r--r--spec/controllers/sessions_controller_spec.rb33
-rw-r--r--spec/controllers/snippets_controller_spec.rb338
70 files changed, 3976 insertions, 1293 deletions
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 4f223811be8..f71f859a704 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -15,6 +15,37 @@ RSpec.describe Admin::ApplicationSettingsController do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
+ describe 'GET #integrations' do
+ before do
+ sign_in(admin)
+ end
+
+ context 'when GitLab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?) { true }
+ end
+
+ it 'returns 404' do
+ get :integrations
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when not GitLab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?) { false }
+ end
+
+ it 'renders correct template' do
+ get :integrations
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('admin/application_settings/integrations')
+ end
+ end
+ end
+
describe 'GET #usage_data with no access' do
before do
stub_usage_data_connections
@@ -56,6 +87,13 @@ RSpec.describe Admin::ApplicationSettingsController do
sign_in(admin)
end
+ it 'updates the require_admin_approval_after_user_signup setting' do
+ put :update, params: { application_setting: { require_admin_approval_after_user_signup: true } }
+
+ expect(response).to redirect_to(general_admin_application_settings_path)
+ expect(ApplicationSetting.current.require_admin_approval_after_user_signup).to eq(true)
+ end
+
it 'updates the password_authentication_enabled_for_git setting' do
put :update, params: { application_setting: { password_authentication_enabled_for_git: "0" } }
diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb
index d2a569a9d48..69bdc79c5f5 100644
--- a/spec/controllers/admin/clusters_controller_spec.rb
+++ b/spec/controllers/admin/clusters_controller_spec.rb
@@ -416,6 +416,7 @@ RSpec.describe Admin::ClustersController do
expect(cluster).to be_user
expect(cluster).to be_kubernetes
expect(cluster).to be_platform_kubernetes_rbac
+ expect(cluster).to be_namespace_per_environment
end
end
end
@@ -585,6 +586,7 @@ RSpec.describe Admin::ClustersController do
enabled: false,
name: 'my-new-cluster-name',
managed: false,
+ namespace_per_environment: false,
base_domain: domain
}
}
@@ -599,6 +601,7 @@ RSpec.describe Admin::ClustersController do
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster).not_to be_managed
+ expect(cluster).not_to be_namespace_per_environment
expect(cluster.domain).to eq('test-domain.com')
end
@@ -624,6 +627,7 @@ RSpec.describe Admin::ClustersController do
enabled: false,
name: 'my-new-cluster-name',
managed: false,
+ namespace_per_environment: false,
domain: domain
}
}
@@ -637,6 +641,7 @@ RSpec.describe Admin::ClustersController do
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster).not_to be_managed
+ expect(cluster).not_to be_namespace_per_environment
end
end
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index 8975f746dd7..17c4222530d 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -29,4 +29,12 @@ RSpec.describe Admin::HooksController do
expect(SystemHook.first).to have_attributes(hook_params)
end
end
+
+ describe 'DELETE #destroy' do
+ let!(:hook) { create(:system_hook) }
+ let!(:log) { create(:web_hook_log, web_hook: hook) }
+ let(:params) { { id: hook } }
+
+ it_behaves_like 'Web hook destroyer'
+ end
end
diff --git a/spec/controllers/admin/instance_review_controller_spec.rb b/spec/controllers/admin/instance_review_controller_spec.rb
new file mode 100644
index 00000000000..d15894eeb5d
--- /dev/null
+++ b/spec/controllers/admin/instance_review_controller_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Admin::InstanceReviewController do
+ include UsageDataHelpers
+
+ let(:admin) { create(:admin) }
+ let(:subscriptions_url) { ::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL }
+
+ before do
+ sign_in(admin)
+ end
+
+ context 'GET #index' do
+ let!(:group) { create(:group) }
+ let!(:projects) { create_list(:project, 2, group: group) }
+
+ subject { post :index }
+
+ context 'with usage ping enabled' do
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ stub_usage_data_connections
+ ::Gitlab::UsageData.data(force_refresh: true)
+ subject
+ end
+
+ it 'redirects to the customers app with correct params' do
+ params = { instance_review: {
+ email: admin.email,
+ last_name: admin.name,
+ version: ::Gitlab::VERSION,
+ users_count: 5,
+ projects_count: 2,
+ groups_count: 1,
+ issues_count: 0,
+ merge_requests_count: 0,
+ internal_pipelines_count: 0,
+ external_pipelines_count: 0,
+ labels_count: 0,
+ milestones_count: 0,
+ snippets_count: 0,
+ notes_count: 0
+ } }.to_query
+
+ expect(response).to redirect_to("#{subscriptions_url}/instance_review?#{params}")
+ end
+ end
+
+ context 'with usage ping disabled' do
+ before do
+ stub_application_setting(usage_ping_enabled: false)
+ subject
+ end
+
+ it 'redirects to the customers app with correct params' do
+ params = { instance_review: {
+ email: admin.email,
+ last_name: admin.name,
+ version: ::Gitlab::VERSION
+ } }.to_query
+
+ expect(response).to redirect_to("#{subscriptions_url}/instance_review?#{params}")
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/integrations_controller_spec.rb b/spec/controllers/admin/integrations_controller_spec.rb
index 4b1806a43d2..1a13d016b73 100644
--- a/spec/controllers/admin/integrations_controller_spec.rb
+++ b/spec/controllers/admin/integrations_controller_spec.rb
@@ -20,6 +20,18 @@ RSpec.describe Admin::IntegrationsController do
end
end
end
+
+ context 'when GitLab.com' do
+ before do
+ allow(::Gitlab).to receive(:com?) { true }
+ end
+
+ it 'returns 404' do
+ get :edit, params: { id: Service.available_services_names.sample }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
describe '#update' do
@@ -43,7 +55,7 @@ RSpec.describe Admin::IntegrationsController do
end
it 'calls to PropagateIntegrationWorker' do
- expect(PropagateIntegrationWorker).to have_received(:perform_async).with(integration.id, false)
+ expect(PropagateIntegrationWorker).to have_received(:perform_async).with(integration.id)
end
end
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 013eee19409..3fffc50475c 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -151,4 +151,21 @@ RSpec.describe Admin::RunnersController do
expect(runner.active).to eq(false)
end
end
+
+ describe 'GET #runner_setup_scripts' do
+ it 'renders the setup scripts' do
+ get :runner_setup_scripts, params: { os: 'linux', arch: 'amd64' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key("install")
+ expect(json_response).to have_key("register")
+ end
+
+ it 'renders errors if they occur' do
+ get :runner_setup_scripts, params: { os: 'foo', arch: 'bar' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to have_key("errors")
+ end
+ end
end
diff --git a/spec/controllers/admin/sessions_controller_spec.rb b/spec/controllers/admin/sessions_controller_spec.rb
index 35982e57034..5fa7a7f278d 100644
--- a/spec/controllers/admin/sessions_controller_spec.rb
+++ b/spec/controllers/admin/sessions_controller_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe Admin::SessionsController, :do_not_mock_admin_mode do
# triggering the auth form will request admin mode
get :new
- Timecop.freeze(Gitlab::Auth::CurrentUserMode::ADMIN_MODE_REQUESTED_GRACE_PERIOD.from_now) do
+ travel_to(Gitlab::Auth::CurrentUserMode::ADMIN_MODE_REQUESTED_GRACE_PERIOD.from_now) do
post :create, params: { user: { password: user.password } }
expect(response).to redirect_to(new_admin_session_path)
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 6301da74f4a..5312a0db7f5 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -23,6 +23,12 @@ RSpec.describe Admin::UsersController do
expect(assigns(:users)).to eq([admin])
end
+
+ it 'eager loads authorized projects association' do
+ get :index
+
+ expect(assigns(:users).first.association(:authorized_projects)).to be_loaded
+ end
end
describe 'GET :id' do
@@ -96,6 +102,58 @@ RSpec.describe Admin::UsersController do
end
end
+ describe 'PUT #approve' do
+ let(:user) { create(:user, :blocked_pending_approval) }
+
+ subject { put :approve, params: { id: user.username } }
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(admin_approval_for_new_user_signups: false)
+ end
+
+ it 'responds with access denied' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(admin_approval_for_new_user_signups: true)
+ end
+
+ context 'when successful' do
+ it 'activates the user' do
+ subject
+
+ user.reload
+
+ expect(user).to be_active
+ expect(flash[:notice]).to eq('Successfully approved')
+ end
+ end
+
+ context 'when unsuccessful' do
+ let(:user) { create(:user, :blocked) }
+
+ it 'displays the error' do
+ subject
+
+ expect(flash[:alert]).to eq('The user you are trying to approve is not pending an approval')
+ end
+
+ it 'does not activate the user' do
+ subject
+
+ user.reload
+ expect(user).not_to be_active
+ end
+ end
+ end
+ end
+
describe 'PUT #activate' do
shared_examples 'a request that activates the user' do
it 'activates the user' do
@@ -184,6 +242,17 @@ RSpec.describe Admin::UsersController do
expect(flash[:notice]).to eq('Error occurred. A blocked user cannot be deactivated')
end
end
+
+ context 'for an internal user' do
+ it 'does not deactivate the user' do
+ internal_user = User.alert_bot
+
+ put :deactivate, params: { id: internal_user.username }
+
+ expect(internal_user.reload.deactivated?).to be_falsey
+ expect(flash[:notice]).to eq('Internal users cannot be deactivated')
+ end
+ end
end
describe 'PUT block/:id' do
@@ -321,7 +390,7 @@ RSpec.describe Admin::UsersController do
describe 'POST update' do
context 'when the password has changed' do
- def update_password(user, password = User.random_password, password_confirmation = password)
+ def update_password(user, password = User.random_password, password_confirmation = password, format = :html)
params = {
id: user.to_param,
user: {
@@ -330,7 +399,7 @@ RSpec.describe Admin::UsersController do
}
}
- post :update, params: params
+ post :update, params: params, format: format
end
context 'when admin changes their own password' do
@@ -429,6 +498,23 @@ RSpec.describe Admin::UsersController do
.not_to change { user.reload.encrypted_password }
end
end
+
+ context 'when the update fails' do
+ let(:password) { User.random_password }
+
+ before do
+ expect_next_instance_of(Users::UpdateService) do |service|
+ allow(service).to receive(:execute).and_return({ message: 'failed', status: :error })
+ end
+ end
+
+ it 'returns a 500 error' do
+ expect { update_password(admin, password, password, :json) }
+ .not_to change { admin.reload.password_expired? }
+
+ expect(response).to have_gitlab_http_status(:error)
+ end
+ end
end
context 'admin notes' do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 188a4cb04af..d95aac2f386 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -416,13 +416,13 @@ RSpec.describe ApplicationController do
end
it 'returns false if the grace period has expired' do
- Timecop.freeze(3.hours.from_now) do
+ travel_to(3.hours.from_now) do
expect(subject).to be_falsey
end
end
it 'returns true if the grace period is still active' do
- Timecop.freeze(1.hour.from_now) do
+ travel_to(1.hour.from_now) do
expect(subject).to be_truthy
end
end
@@ -844,6 +844,8 @@ RSpec.describe ApplicationController do
describe '#set_current_context' do
controller(described_class) do
+ feature_category :issue_tracking
+
def index
Labkit::Context.with_context do |context|
render json: context.to_h
@@ -893,6 +895,12 @@ RSpec.describe ApplicationController do
expect(json_response['meta.caller_id']).to eq('AnonymousController#index')
end
+ it 'sets the feature_category as defined in the controller' do
+ get :index, format: :json
+
+ expect(json_response['meta.feature_category']).to eq('issue_tracking')
+ end
+
it 'assigns the context to a variable for logging' do
get :index, format: :json
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index c72d9e5053a..9b09f46d17e 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -260,6 +260,17 @@ RSpec.describe Boards::ListsController do
end
end
+ context 'with an error service response' do
+ it 'returns an unprocessable entity response' do
+ allow(Boards::Lists::DestroyService).to receive(:new)
+ .and_return(double(execute: ServiceResponse.error(message: 'error')))
+
+ remove_board_list user: user, board: board, list: planning
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
def remove_board_list(user:, board:, list:)
sign_in(user)
diff --git a/spec/controllers/concerns/controller_with_feature_category/config_spec.rb b/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
deleted file mode 100644
index 9b8ffd2baab..00000000000
--- a/spec/controllers/concerns/controller_with_feature_category/config_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require "fast_spec_helper"
-require "rspec-parameterized"
-require_relative "../../../../app/controllers/concerns/controller_with_feature_category/config"
-
-RSpec.describe ControllerWithFeatureCategory::Config do
- describe "#matches?" do
- using RSpec::Parameterized::TableSyntax
-
- where(:only_actions, :except_actions, :if_proc, :unless_proc, :test_action, :expected) do
- nil | nil | nil | nil | "action" | true
- [:included] | nil | nil | nil | "action" | false
- [:included] | nil | nil | nil | "included" | true
- nil | [:excluded] | nil | nil | "excluded" | false
- nil | nil | true | nil | "action" | true
- [:included] | nil | true | nil | "action" | false
- [:included] | nil | true | nil | "included" | true
- nil | [:excluded] | true | nil | "excluded" | false
- nil | nil | false | nil | "action" | false
- [:included] | nil | false | nil | "action" | false
- [:included] | nil | false | nil | "included" | false
- nil | [:excluded] | false | nil | "excluded" | false
- nil | nil | nil | true | "action" | false
- [:included] | nil | nil | true | "action" | false
- [:included] | nil | nil | true | "included" | false
- nil | [:excluded] | nil | true | "excluded" | false
- nil | nil | nil | false | "action" | true
- [:included] | nil | nil | false | "action" | false
- [:included] | nil | nil | false | "included" | true
- nil | [:excluded] | nil | false | "excluded" | false
- nil | nil | true | false | "action" | true
- [:included] | nil | true | false | "action" | false
- [:included] | nil | true | false | "included" | true
- nil | [:excluded] | true | false | "excluded" | false
- nil | nil | false | true | "action" | false
- [:included] | nil | false | true | "action" | false
- [:included] | nil | false | true | "included" | false
- nil | [:excluded] | false | true | "excluded" | false
- end
-
- with_them do
- let(:config) do
- if_to_proc = if_proc.nil? ? nil : -> (_) { if_proc }
- unless_to_proc = unless_proc.nil? ? nil : -> (_) { unless_proc }
-
- described_class.new(:category, only_actions, except_actions, if_to_proc, unless_to_proc)
- end
-
- specify { expect(config.matches?(test_action)).to be(expected) }
- end
- end
-end
diff --git a/spec/controllers/concerns/controller_with_feature_category_spec.rb b/spec/controllers/concerns/controller_with_feature_category_spec.rb
index e603a7d14c4..55e84755f5c 100644
--- a/spec/controllers/concerns/controller_with_feature_category_spec.rb
+++ b/spec/controllers/concerns/controller_with_feature_category_spec.rb
@@ -2,7 +2,6 @@
require 'fast_spec_helper'
require_relative "../../../app/controllers/concerns/controller_with_feature_category"
-require_relative "../../../app/controllers/concerns/controller_with_feature_category/config"
RSpec.describe ControllerWithFeatureCategory do
describe ".feature_category_for_action" do
@@ -14,17 +13,15 @@ RSpec.describe ControllerWithFeatureCategory do
let(:controller) do
Class.new(base_controller) do
- feature_category :baz
- feature_category :foo, except: %w(update edit)
- feature_category :bar, only: %w(index show)
- feature_category :quux, only: %w(destroy)
- feature_category :quuz, only: %w(destroy)
+ feature_category :foo, %w(update edit)
+ feature_category :bar, %w(index show)
+ feature_category :quux, %w(destroy)
end
end
let(:subclass) do
Class.new(controller) do
- feature_category :qux, only: %w(index)
+ feature_category :baz, %w(subclass_index)
end
end
@@ -33,34 +30,31 @@ RSpec.describe ControllerWithFeatureCategory do
end
it "returns the expected category", :aggregate_failures do
- expect(controller.feature_category_for_action("update")).to eq(:baz)
- expect(controller.feature_category_for_action("hello")).to eq(:foo)
+ expect(controller.feature_category_for_action("update")).to eq(:foo)
expect(controller.feature_category_for_action("index")).to eq(:bar)
+ expect(controller.feature_category_for_action("destroy")).to eq(:quux)
end
- it "returns the closest match for categories defined in subclasses" do
- expect(subclass.feature_category_for_action("index")).to eq(:qux)
- expect(subclass.feature_category_for_action("show")).to eq(:bar)
+ it "returns the expected category for categories defined in subclasses" do
+ expect(subclass.feature_category_for_action("subclass_index")).to eq(:baz)
end
- it "returns the last defined feature category when multiple match" do
- expect(controller.feature_category_for_action("destroy")).to eq(:quuz)
- end
-
- it "raises an error when using including and excluding the same action" do
+ it "raises an error when defining for the controller and for individual actions" do
expect do
Class.new(base_controller) do
- feature_category :hello, only: [:world], except: [:world]
+ feature_category :hello
+ feature_category :goodbye, [:world]
end
- end.to raise_error(%r(cannot configure both `only` and `except`))
+ end.to raise_error(ArgumentError, "hello is defined for all actions, but other categories are set")
end
- it "raises an error when using unknown arguments" do
+ it "raises an error when multiple calls define the same action" do
expect do
Class.new(base_controller) do
- feature_category :hello, hello: :world
+ feature_category :hello, [:world]
+ feature_category :goodbye, ["world"]
end
- end.to raise_error(%r(unknown arguments))
+ end.to raise_error(ArgumentError, "Actions have multiple feature categories: world")
end
end
end
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index befdd760965..6fa273bf3d7 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe IssuableCollections do
+ using RSpec::Parameterized::TableSyntax
+
let(:user) { create(:user) }
let(:controller) do
@@ -25,13 +27,35 @@ RSpec.describe IssuableCollections do
end
describe '#page_count_for_relation' do
- let(:params) { { state: 'opened' } }
+ let(:relation) { double(:relation, limit_value: 20) }
+
+ context 'row count is known' do
+ let(:params) { { state: 'opened' } }
+
+ it 'returns the number of pages' do
+ pages = controller.send(:page_count_for_relation, relation, 28)
+
+ expect(pages).to eq(2)
+ end
+ end
+
+ context 'row_count is unknown' do
+ where(:page_param, :expected) do
+ nil | 2
+ 1 | 2
+ '1' | 2
+ 2 | 3
+ end
- it 'returns the number of pages' do
- relation = double(:relation, limit_value: 20)
- pages = controller.send(:page_count_for_relation, relation, 28)
+ with_them do
+ let(:params) { { state: 'opened', page: page_param } }
- expect(pages).to eq(2)
+ it 'returns current page + 1 if the row count is unknown' do
+ pages = controller.send(:page_count_for_relation, relation, -1)
+
+ expect(pages).to eq(expected)
+ end
+ end
end
end
diff --git a/spec/controllers/concerns/redis_tracking_spec.rb b/spec/controllers/concerns/redis_tracking_spec.rb
index 3795fca5576..831f5ad7bb1 100644
--- a/spec/controllers/concerns/redis_tracking_spec.rb
+++ b/spec/controllers/concerns/redis_tracking_spec.rb
@@ -3,15 +3,19 @@
require "spec_helper"
RSpec.describe RedisTracking do
- let(:event_name) { 'g_compliance_dashboard' }
- let(:feature) { 'g_compliance_dashboard_feature' }
+ let(:feature) { 'approval_rule' }
let(:user) { create(:user) }
+ before do
+ skip_feature_flags_yaml_validation
+ end
+
controller(ApplicationController) do
include RedisTracking
skip_before_action :authenticate_user!, only: :show
- track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :g_compliance_dashboard_feature, feature_default_enabled: true
+ track_redis_hll_event :index, :show, name: 'g_compliance_approval_rules', feature: :approval_rule, feature_default_enabled: true,
+ if: [:custom_condition_one?, :custom_condition_two?]
def index
render html: 'index'
@@ -24,51 +28,94 @@ RSpec.describe RedisTracking do
def show
render html: 'show'
end
- end
- context 'with feature disabled' do
- it 'does not track the event' do
- stub_feature_flags(feature => false)
+ private
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ def custom_condition_one?
+ true
+ end
- get :index
+ def custom_condition_two?
+ true
end
end
- context 'with usage ping disabled' do
+ def expect_tracking
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ .with(instance_of(String), 'g_compliance_approval_rules')
+ end
+
+ def expect_no_tracking
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ end
+
+ context 'with feature disabled' do
it 'does not track the event' do
- stub_feature_flags(feature => true)
- allow(Gitlab::CurrentSettings).to receive(:usage_ping_enabled?).and_return(false)
+ stub_feature_flags(feature => false)
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ expect_no_tracking
get :index
end
end
- context 'with feature enabled and usage ping enabled' do
+ context 'with feature enabled' do
before do
stub_feature_flags(feature => true)
- allow(Gitlab::CurrentSettings).to receive(:usage_ping_enabled?).and_return(true)
end
context 'when user is logged in' do
- it 'tracks the event' do
+ before do
sign_in(user)
+ end
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ it 'tracks the event' do
+ expect_tracking
get :index
end
it 'passes default_enabled flag' do
- sign_in(user)
-
expect(controller).to receive(:metric_feature_enabled?).with(feature.to_sym, true)
get :index
end
+
+ it 'tracks the event if DNT is not enabled' do
+ request.headers['DNT'] = '0'
+
+ expect_tracking
+
+ get :index
+ end
+
+ it 'does not track the event if DNT is enabled' do
+ request.headers['DNT'] = '1'
+
+ expect_no_tracking
+
+ get :index
+ end
+
+ it 'does not track the event if the format is not HTML' do
+ expect_no_tracking
+
+ get :index, format: :json
+ end
+
+ it 'does not track the event if a custom condition returns false' do
+ expect(controller).to receive(:custom_condition_two?).and_return(false)
+
+ expect_no_tracking
+
+ get :index
+ end
+
+ it 'does not track the event for untracked actions' do
+ expect_no_tracking
+
+ get :new
+ end
end
context 'when user is not logged in and there is a visitor_id' do
@@ -81,26 +128,18 @@ RSpec.describe RedisTracking do
it 'tracks the event' do
cookies[:visitor_id] = { value: visitor_id, expires: 24.months }
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+ expect_tracking
get :show
end
end
context 'when user is not logged in and there is no visitor_id' do
- it 'does not tracks the event' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ it 'does not track the event' do
+ expect_no_tracking
get :index
end
end
-
- context 'for untracked action' do
- it 'does not tracks the event' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
-
- get :new
- end
- end
end
end
diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb
index 415cb821545..e7091664d1a 100644
--- a/spec/controllers/dashboard/labels_controller_spec.rb
+++ b/spec/controllers/dashboard/labels_controller_spec.rb
@@ -3,27 +3,32 @@
require 'spec_helper'
RSpec.describe Dashboard::LabelsController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let!(:label) { create(:label, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_2) { create(:project) }
+
+ let_it_be(:label) { create(:label, project: project, title: 'some_label') }
+ let_it_be(:label_with_same_title) { create(:label, project: project_2, title: 'some_label') }
+ let_it_be(:unrelated_label) { create(:label, project: create(:project, :public)) }
+
+ before_all do
+ project.add_reporter(user)
+ project_2.add_reporter(user)
+ end
before do
sign_in(user)
- project.add_reporter(user)
end
describe "#index" do
- let!(:unrelated_label) { create(:label, project: create(:project, :public)) }
-
subject { get :index, format: :json }
- it 'returns global labels for projects the user has a relationship with' do
+ it 'returns labels with unique titles for projects the user has a relationship with' do
subject
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1)
- expect(json_response[0]["id"]).to be_nil
- expect(json_response[0]["title"]).to eq(label.title)
+ expect(json_response[0]['title']).to eq(label.title)
end
it_behaves_like 'disabled when using an external authorization service'
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index c838affa239..9b78f841cce 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -15,6 +15,16 @@ RSpec.describe DashboardController do
describe 'GET issues' do
it_behaves_like 'issuables list meta-data', :issue, :issues
it_behaves_like 'issuables requiring filter', :issues
+
+ it 'lists only incidents and issues' do
+ issue = create(:incident, project: project, author: user)
+ incident = create(:incident, project: project, author: user)
+ create(:quality_test_case, project: project, author: user)
+
+ get :issues, params: { author_id: user.id }
+
+ expect(assigns(:issues)).to match_array([issue, incident])
+ end
end
describe 'GET merge requests' do
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
index 4785ee9ed8f..b1519c4ef1e 100644
--- a/spec/controllers/every_controller_spec.rb
+++ b/spec/controllers/every_controller_spec.rb
@@ -17,20 +17,20 @@ RSpec.describe "Every controller" do
.compact
.select { |route| route[:controller].present? && route[:action].present? }
.map { |route| [constantize_controller(route[:controller]), route[:action]] }
- .reject { |route| route.first.nil? || !route.first.include?(ControllerWithFeatureCategory) }
+ .select { |(controller, action)| controller&.include?(ControllerWithFeatureCategory) }
+ .reject { |(controller, action)| controller == ApplicationController || controller == Devise::UnlocksController }
end
let_it_be(:routes_without_category) do
controller_actions.map do |controller, action|
- "#{controller}##{action}" unless controller.feature_category_for_action(action)
+ next if controller.feature_category_for_action(action)
+
+ "#{controller}##{action}"
end.compact
end
it "has feature categories" do
- pending("We'll work on defining categories for all controllers: "\
- "https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/463")
-
- expect(routes_without_category).to be_empty, "#{routes_without_category.first(10)} did not have a category"
+ expect(routes_without_category).to be_empty, "#{routes_without_category} did not have a category"
end
it "completed controllers don't get new routes without categories" do
@@ -74,9 +74,9 @@ RSpec.describe "Every controller" do
end
def actions_defined_in_feature_category_config(controller)
- feature_category_configs = controller.send(:class_attributes)[:feature_category_config]
- feature_category_configs.map do |config|
- Array(config.send(:only)) + Array(config.send(:except))
- end.flatten.uniq.map(&:to_s)
+ controller.send(:class_attributes)[:feature_category_config]
+ .values
+ .flatten
+ .map(&:to_s)
end
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 405f1eae482..e4aea688a69 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -98,6 +98,12 @@ RSpec.describe GraphqlController do
expect(assigns(:context)[:is_sessionless_user]).to be false
end
end
+
+ it 'includes request object in context' do
+ post :execute
+
+ expect(assigns(:context)[:request]).to eq request
+ end
end
describe 'Admin Mode' do
@@ -150,9 +156,11 @@ RSpec.describe GraphqlController do
describe '#append_info_to_payload' do
let(:graphql_query) { graphql_query_for('project', { 'fullPath' => 'foo' }, %w(id name)) }
+ let(:mock_store) { { graphql_logs: { foo: :bar } } }
let(:log_payload) { {} }
before do
+ allow(RequestStore).to receive(:store).and_return(mock_store)
allow(controller).to receive(:append_info_to_payload).and_wrap_original do |method, *|
method.call(log_payload)
end
@@ -162,7 +170,7 @@ RSpec.describe GraphqlController do
post :execute, params: { query: graphql_query, operationName: 'Foo' }
expect(controller).to have_received(:append_info_to_payload)
- expect(log_payload.dig(:metadata, :graphql, :operation_name)).to eq('Foo')
+ expect(log_payload.dig(:metadata, :graphql)).to eq({ operation_name: 'Foo', foo: :bar })
end
end
end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 81d5bc7770f..140b7b0f2a8 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -271,6 +271,7 @@ RSpec.describe Groups::ClustersController do
expect(cluster).to be_kubernetes
expect(cluster.provider_gcp).to be_legacy_abac
expect(cluster).to be_managed
+ expect(cluster).to be_namespace_per_environment
end
context 'when legacy_abac param is false' do
@@ -358,6 +359,7 @@ RSpec.describe Groups::ClustersController do
expect(cluster).to be_user
expect(cluster).to be_kubernetes
expect(cluster).to be_managed
+ expect(cluster).to be_namespace_per_environment
end
end
@@ -387,6 +389,7 @@ RSpec.describe Groups::ClustersController do
expect(cluster).to be_user
expect(cluster).to be_kubernetes
expect(cluster).to be_platform_kubernetes_rbac
+ expect(cluster).to be_namespace_per_environment
end
end
@@ -716,6 +719,7 @@ RSpec.describe Groups::ClustersController do
enabled: false,
name: 'my-new-cluster-name',
managed: false,
+ namespace_per_environment: false,
domain: domain
}
}
@@ -729,6 +733,7 @@ RSpec.describe Groups::ClustersController do
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster).not_to be_managed
+ expect(cluster).not_to be_namespace_per_environment
end
end
diff --git a/spec/controllers/groups/group_links_controller_spec.rb b/spec/controllers/groups/group_links_controller_spec.rb
index 07299382230..c411d9cfb63 100644
--- a/spec/controllers/groups/group_links_controller_spec.rb
+++ b/spec/controllers/groups/group_links_controller_spec.rb
@@ -15,6 +15,21 @@ RSpec.describe Groups::GroupLinksController do
shared_with_group.add_developer(group_member)
end
+ shared_examples 'placeholder is passed as `id` parameter' do |action|
+ it 'returns a 404' do
+ post(
+ action,
+ params: {
+ group_id: shared_group,
+ id: ':id'
+ },
+ format: :json
+ )
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
describe '#create' do
let(:shared_with_group_id) { shared_with_group.id }
let(:shared_group_access) { GroupGroupLink.default_access }
@@ -125,6 +140,8 @@ RSpec.describe Groups::GroupLinksController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ include_examples 'placeholder is passed as `id` parameter', :create
end
describe '#update' do
@@ -136,10 +153,15 @@ RSpec.describe Groups::GroupLinksController do
let(:expiry_date) { 1.month.from_now.to_date }
subject do
- post(:update, params: { group_id: shared_group,
- id: link.id,
- group_link: { group_access: Gitlab::Access::GUEST,
- expires_at: expiry_date } })
+ post(
+ :update,
+ params: {
+ group_id: shared_group,
+ id: link.id,
+ group_link: { group_access: Gitlab::Access::GUEST, expires_at: expiry_date }
+ },
+ format: :json
+ )
end
context 'when user has admin access to the shared group' do
@@ -160,6 +182,26 @@ RSpec.describe Groups::GroupLinksController do
expect(link.expires_at).to eq(expiry_date)
end
+ context 'when `expires_at` is set' do
+ it 'returns correct json response' do
+ travel_to Time.now.utc.beginning_of_day
+
+ subject
+
+ expect(json_response).to eq({ "expires_in" => "about 1 month", "expires_soon" => false })
+ end
+ end
+
+ context 'when `expires_at` is not set' do
+ let(:expiry_date) { nil }
+
+ it 'returns empty json response' do
+ subject
+
+ expect(json_response).to be_empty
+ end
+ end
+
it 'updates project permissions' do
expect { subject }.to change { group_member.can?(:create_release, project) }.from(true).to(false)
end
@@ -172,6 +214,8 @@ RSpec.describe Groups::GroupLinksController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ include_examples 'placeholder is passed as `id` parameter', :update
end
describe '#destroy' do
@@ -207,5 +251,7 @@ RSpec.describe Groups::GroupLinksController do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ include_examples 'placeholder is passed as `id` parameter', :destroy
end
end
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 4b9dd3629f1..5425a437c80 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -233,6 +233,42 @@ RSpec.describe Groups::GroupMembersController do
end
end
end
+
+ context 'expiration date' do
+ let(:expiry_date) { 1.month.from_now.to_date }
+
+ before do
+ travel_to Time.now.utc.beginning_of_day
+
+ put(
+ :update,
+ params: {
+ group_member: { expires_at: expiry_date },
+ group_id: group,
+ id: requester
+ },
+ format: :json
+ )
+ end
+
+ context 'when `expires_at` is set' do
+ it 'returns correct json response' do
+ expect(json_response).to eq({
+ "expires_in" => "about 1 month",
+ "expires_soon" => false,
+ "expires_at_formatted" => expiry_date.to_time.in_time_zone.to_s(:medium)
+ })
+ end
+ end
+
+ context 'when `expires_at` is not set' do
+ let(:expiry_date) { nil }
+
+ it 'returns empty json response' do
+ expect(json_response).to be_empty
+ end
+ end
+ end
end
describe 'DELETE destroy' do
@@ -441,7 +477,7 @@ RSpec.describe Groups::GroupMembersController do
group_id: group,
id: membership
},
- format: :js
+ format: :json
expect(response).to have_gitlab_http_status(:ok)
end
diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb
index 20ee19b01d1..33041f1af9f 100644
--- a/spec/controllers/groups/labels_controller_spec.rb
+++ b/spec/controllers/groups/labels_controller_spec.rb
@@ -9,6 +9,8 @@ RSpec.describe Groups::LabelsController do
before do
group.add_owner(user)
+ # by default FFs are enabled in specs so we turn it off
+ stub_feature_flags(show_inherited_labels: false)
sign_in(user)
end
@@ -32,11 +34,41 @@ RSpec.describe Groups::LabelsController do
subgroup.add_owner(user)
end
- it 'returns ancestor group labels' do
- get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json
+ RSpec.shared_examples 'returns ancestor group labels' do
+ it 'returns ancestor group labels' do
+ get :index, params: params, format: :json
- label_ids = json_response.map {|label| label['title']}
- expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title])
+ label_ids = json_response.map {|label| label['title']}
+ expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title])
+ end
+ end
+
+ context 'when include_ancestor_groups true' do
+ let(:params) { { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true } }
+
+ it_behaves_like 'returns ancestor group labels'
+ end
+
+ context 'when include_ancestor_groups false' do
+ let(:params) { { group_id: subgroup, only_group_labels: true } }
+
+ it 'does not return ancestor group labels', :aggregate_failures do
+ get :index, params: params, format: :json
+
+ label_ids = json_response.map {|label| label['title']}
+ expect(label_ids).to match_array([subgroup_label_1.title])
+ expect(label_ids).not_to include([group_label_1.title])
+ end
+ end
+
+ context 'when show_inherited_labels enabled' do
+ let(:params) { { group_id: subgroup } }
+
+ before do
+ stub_feature_flags(show_inherited_labels: true)
+ end
+
+ it_behaves_like 'returns ancestor group labels'
end
end
@@ -56,4 +88,43 @@ RSpec.describe Groups::LabelsController do
expect(response).to have_gitlab_http_status(:ok)
end
end
+
+ describe 'DELETE #destroy' do
+ context 'when current user has ability to destroy the label' do
+ before do
+ sign_in(user)
+ end
+
+ it 'removes the label' do
+ label = create(:group_label, group: group)
+ delete :destroy, params: { group_id: group.to_param, id: label.to_param }
+
+ expect { label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ context 'when label is succesfuly destroyed' do
+ it 'redirects to the group labels page' do
+ label = create(:group_label, group: group)
+ delete :destroy, params: { group_id: group.to_param, id: label.to_param }
+
+ expect(response).to redirect_to(group_labels_path)
+ end
+ end
+ end
+
+ context 'when current_user does not have ability to destroy the label' do
+ let(:another_user) { create(:user) }
+
+ before do
+ sign_in(another_user)
+ end
+
+ it 'responds with status 404' do
+ label = create(:group_label, group: group)
+ delete :destroy, params: { group_id: group.to_param, id: label.to_param }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 5c7b88a218a..2c85fe482e2 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe Groups::MilestonesController do
let(:user) { create(:user) }
let(:title) { '肯定不是中文的问题' }
let(:milestone) { create(:milestone, project: project) }
- let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) }
let(:milestone_params) do
{
@@ -25,6 +24,12 @@ RSpec.describe Groups::MilestonesController do
project.add_maintainer(user)
end
+ it_behaves_like 'milestone tabs' do
+ let(:milestone) { create(:milestone, group: group) }
+ let(:milestone_path) { group_milestone_path(group, milestone.iid) }
+ let(:request_params) { { group_id: group, id: milestone.iid } }
+ end
+
describe '#index' do
describe 'as HTML' do
render_views
diff --git a/spec/controllers/groups/registry/repositories_controller_spec.rb b/spec/controllers/groups/registry/repositories_controller_spec.rb
index ddac8fc5002..ae982b02a4f 100644
--- a/spec/controllers/groups/registry/repositories_controller_spec.rb
+++ b/spec/controllers/groups/registry/repositories_controller_spec.rb
@@ -87,7 +87,7 @@ RSpec.describe Groups::Registry::RepositoriesController do
it_behaves_like 'with name parameter'
- it_behaves_like 'a gitlab tracking event', described_class.name, 'list_repositories'
+ it_behaves_like 'a package tracking event', described_class.name, 'list_repositories'
context 'with project in subgroup' do
let_it_be(:test_group) { create(:group, parent: group ) }
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index f11bb66caab..880d5fe8951 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -225,4 +225,25 @@ RSpec.describe Groups::Settings::CiCdController do
end
end
end
+
+ describe 'GET #runner_setup_scripts' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'renders the setup scripts' do
+ get :runner_setup_scripts, params: { os: 'linux', arch: 'amd64', group_id: group }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key("install")
+ expect(json_response).to have_key("register")
+ end
+
+ it 'renders errors if they occur' do
+ get :runner_setup_scripts, params: { os: 'foo', arch: 'bar', group_id: group }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to have_key("errors")
+ end
+ end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 35d8c0b7c6d..df7e018b35e 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -2,18 +2,18 @@
require 'spec_helper'
-RSpec.describe GroupsController do
+RSpec.describe GroupsController, factory_default: :keep do
include ExternalAuthorizationServiceHelpers
- let(:user) { create(:user) }
- let(:admin) { create(:admin) }
- let(:group) { create(:group, :public) }
- let(:project) { create(:project, namespace: group) }
- let!(:group_member) { create(:group_member, group: group, user: user) }
- let!(:owner) { group.add_owner(create(:user)).user }
- let!(:maintainer) { group.add_maintainer(create(:user)).user }
- let!(:developer) { group.add_developer(create(:user)).user }
- let!(:guest) { group.add_guest(create(:user)).user }
+ let_it_be_with_refind(:group) { create_default(:group, :public) }
+ let_it_be_with_refind(:project) { create(:project, namespace: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:group_member) { create(:group_member, group: group, user: user) }
+ let_it_be(:owner) { group.add_owner(create(:user)).user }
+ let_it_be(:maintainer) { group.add_maintainer(create(:user)).user }
+ let_it_be(:developer) { group.add_developer(create(:user)).user }
+ let_it_be(:guest) { group.add_guest(create(:user)).user }
shared_examples 'member with ability to create subgroups' do
it 'renders the new page' do
@@ -57,7 +57,6 @@ RSpec.describe GroupsController do
describe 'GET #show' do
before do
sign_in(user)
- project
end
let(:format) { :html }
@@ -82,7 +81,6 @@ RSpec.describe GroupsController do
describe 'GET #details' do
before do
sign_in(user)
- project
end
let(:format) { :html }
@@ -131,12 +129,9 @@ RSpec.describe GroupsController do
end
describe 'GET #activity' do
- render_views
-
context 'as json' do
before do
sign_in(user)
- project
end
it 'includes events from all projects in group and subgroups', :sidekiq_might_not_need_inline do
@@ -157,10 +152,6 @@ RSpec.describe GroupsController do
end
context 'when user has no permission to see the event' do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
-
let(:project_with_restricted_access) do
create(:project, :public, issues_access_level: ProjectFeature::PRIVATE, group: group)
end
@@ -398,8 +389,8 @@ RSpec.describe GroupsController do
end
describe 'GET #issues', :sidekiq_might_not_need_inline do
- let(:issue_1) { create(:issue, project: project, title: 'foo') }
- let(:issue_2) { create(:issue, project: project, title: 'bar') }
+ let_it_be(:issue_1) { create(:issue, project: project, title: 'foo') }
+ let_it_be(:issue_2) { create(:issue, project: project, title: 'bar') }
before do
create_list(:award_emoji, 3, awardable: issue_2)
@@ -409,6 +400,15 @@ RSpec.describe GroupsController do
sign_in(user)
end
+ it 'lists only incidents and issues' do
+ incident = create(:incident, project: project)
+ create(:quality_test_case, project: project)
+
+ get :issues, params: { id: group.to_param }
+
+ expect(assigns(:issues)).to match_array([issue_1, issue_2, incident])
+ end
+
context 'sorting by votes' do
it 'sorts most popular issues' do
get :issues, params: { id: group.to_param, sort: 'upvotes_desc' }
@@ -551,9 +551,38 @@ RSpec.describe GroupsController do
end
end
- context 'when there is a conflicting group path' do
- render_views
+ context "updating default_branch_name" do
+ let(:example_branch_name) { "example_branch_name" }
+
+ subject(:update_action) do
+ put :update,
+ params: {
+ id: group.to_param,
+ group: { default_branch_name: example_branch_name }
+ }
+ end
+
+ it "updates the attribute" do
+ expect { subject }
+ .to change { group.namespace_settings.reload.default_branch_name }
+ .from(nil)
+ .to(example_branch_name)
+
+ expect(response).to have_gitlab_http_status(:found)
+ end
+ context "to empty string" do
+ let(:example_branch_name) { '' }
+
+ it "does not update the attribute" do
+ subject
+
+ expect(group.namespace_settings.reload.default_branch_name).not_to eq('')
+ end
+ end
+ end
+
+ context 'when there is a conflicting group path' do
let!(:conflict_group) { create(:group, path: SecureRandom.hex(12) ) }
let!(:old_name) { group.name }
@@ -794,6 +823,7 @@ RSpec.describe GroupsController do
context 'when transferring to a subgroup goes right' do
let(:new_parent_group) { create(:group, :public) }
+ let(:group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
let!(:new_parent_group_member) { create(:group_member, :owner, group: new_parent_group, user: user) }
@@ -805,11 +835,8 @@ RSpec.describe GroupsController do
}
end
- it 'returns a notice' do
+ it 'returns a notice and redirects to the new path' do
expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.")
- end
-
- it 'redirects to the new path' do
expect(response).to redirect_to("/#{new_parent_group.path}/#{group.path}")
end
end
@@ -826,17 +853,15 @@ RSpec.describe GroupsController do
}
end
- it 'returns a notice' do
+ it 'returns a notice and redirects to the new path' do
expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.")
- end
-
- it 'redirects to the new path' do
expect(response).to redirect_to("/#{group.path}")
end
end
context 'When the transfer goes wrong' do
let(:new_parent_group) { create(:group, :public) }
+ let(:group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
let!(:new_parent_group_member) { create(:group_member, :owner, group: new_parent_group, user: user) }
@@ -850,17 +875,15 @@ RSpec.describe GroupsController do
}
end
- it 'returns an alert' do
+ it 'returns an alert and redirects to the current path' do
expect(flash[:alert]).to eq "Transfer failed: namespace directory cannot be moved"
- end
-
- it 'redirects to the current path' do
expect(response).to redirect_to(edit_group_path(group))
end
end
context 'when the user is not allowed to transfer the group' do
let(:new_parent_group) { create(:group, :public) }
+ let(:group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
let!(:new_parent_group_member) { create(:group_member, :guest, group: new_parent_group, user: user) }
@@ -879,6 +902,7 @@ RSpec.describe GroupsController do
context 'transferring when a project has container images' do
let(:group) { create(:group, :public, :nested) }
+ let(:project) { create(:project, namespace: group) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
before do
@@ -979,6 +1003,8 @@ RSpec.describe GroupsController do
end
context 'when there is no file available to download' do
+ let(:admin) { create(:admin) }
+
before do
sign_in(admin)
end
@@ -1149,9 +1175,7 @@ RSpec.describe GroupsController do
describe "GET #activity as JSON" do
include DesignManagementTestHelpers
- render_views
- let(:project) { create(:project, :public, group: group) }
let(:other_project) { create(:project, :public, group: group) }
def get_activity
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 3049396dd0f..9ac42cbc3ec 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe HelpController do
+ include StubVersion
+
let(:user) { create(:user) }
before do
@@ -108,8 +110,56 @@ RSpec.describe HelpController do
end
it 'renders HTML' do
- expect(response).to render_template('show.html.haml')
- expect(response.media_type).to eq 'text/html'
+ aggregate_failures do
+ expect(response).to render_template('show.html.haml')
+ expect(response.media_type).to eq 'text/html'
+ end
+ end
+ end
+
+ context 'when a custom help_page_documentation_url is set' do
+ before do
+ stub_application_setting(help_page_documentation_base_url: documentation_base_url)
+ stub_version(gitlab_version, 'deadbeaf')
+ end
+
+ subject { get :show, params: { path: path }, format: 'html' }
+
+ let(:gitlab_version) { '13.4.0-ee' }
+ let(:documentation_base_url) { 'https://docs.gitlab.com' }
+ let(:path) { 'ssh/README' }
+
+ it 'redirects user to custom documentation url with a specified version' do
+ is_expected.to redirect_to("#{documentation_base_url}/13.4/ee/#{path}.html")
+ end
+
+ context 'when documentation url ends with a slash' do
+ let(:documentation_base_url) { 'https://docs.gitlab.com/' }
+
+ it 'redirects user to custom documentation url without slash duplicates' do
+ is_expected.to redirect_to("https://docs.gitlab.com/13.4/ee/#{path}.html")
+ end
+ end
+
+ context 'when it is a pre-release' do
+ let(:gitlab_version) { '13.4.0-pre' }
+
+ it 'redirects user to custom documentation url without a version' do
+ is_expected.to redirect_to("#{documentation_base_url}/ee/#{path}.html")
+ end
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(help_page_documentation_redirect: false)
+ end
+
+ it 'renders HTML' do
+ aggregate_failures do
+ is_expected.to render_template('show.html.haml')
+ expect(response.media_type).to eq 'text/html'
+ end
+ end
end
end
@@ -129,9 +179,12 @@ RSpec.describe HelpController do
path: 'user/img/markdown_logo'
},
format: :png
- expect(response).to be_successful
- expect(response.media_type).to eq 'image/png'
- expect(response.headers['Content-Disposition']).to match(/^inline;/)
+
+ aggregate_failures do
+ expect(response).to be_successful
+ expect(response.media_type).to eq 'image/png'
+ expect(response.headers['Content-Disposition']).to match(/^inline;/)
+ end
end
end
diff --git a/spec/controllers/import/bulk_imports_controller_spec.rb b/spec/controllers/import/bulk_imports_controller_spec.rb
new file mode 100644
index 00000000000..f3850ff844e
--- /dev/null
+++ b/spec/controllers/import/bulk_imports_controller_spec.rb
@@ -0,0 +1,179 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Import::BulkImportsController do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when user is signed in' do
+ context 'when bulk_import feature flag is enabled' do
+ before do
+ stub_feature_flags(bulk_import: true)
+ end
+
+ describe 'POST configure' do
+ context 'when no params are passed in' do
+ it 'clears out existing session' do
+ post :configure
+
+ expect(session[:bulk_import_gitlab_access_token]).to be_nil
+ expect(session[:bulk_import_gitlab_url]).to be_nil
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(status_import_bulk_import_url)
+ end
+ end
+
+ it 'sets the session variables' do
+ token = 'token'
+ url = 'https://gitlab.example'
+
+ post :configure, params: { bulk_import_gitlab_access_token: token, bulk_import_gitlab_url: url }
+
+ expect(session[:bulk_import_gitlab_access_token]).to eq(token)
+ expect(session[:bulk_import_gitlab_url]).to eq(url)
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(status_import_bulk_import_url)
+ end
+
+ it 'strips access token with spaces' do
+ token = 'token'
+
+ post :configure, params: { bulk_import_gitlab_access_token: " #{token} " }
+
+ expect(session[:bulk_import_gitlab_access_token]).to eq(token)
+ expect(controller).to redirect_to(status_import_bulk_import_url)
+ end
+ end
+
+ describe 'GET status' do
+ let(:client) { Gitlab::BulkImport::Client.new(uri: 'http://gitlab.example', token: 'token') }
+
+ describe 'serialized group data' do
+ let(:client_response) do
+ [
+ { 'id' => 1, 'full_name' => 'group1', 'full_path' => 'full/path/group1' },
+ { 'id' => 2, 'full_name' => 'group2', 'full_path' => 'full/path/group2' }
+ ]
+ end
+
+ before do
+ allow(controller).to receive(:client).and_return(client)
+ allow(client).to receive(:get).with('groups', top_level_only: true).and_return(client_response)
+ end
+
+ it 'returns serialized group data' do
+ get :status, format: :json
+
+ expect(response.parsed_body).to eq({ importable_data: client_response }.as_json)
+ end
+ end
+
+ context 'when host url is local or not http' do
+ %w[https://localhost:3000 http://192.168.0.1 ftp://testing].each do |url|
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+
+ session[:bulk_import_gitlab_access_token] = 'test'
+ session[:bulk_import_gitlab_url] = url
+ end
+
+ it 'denies network request' do
+ get :status
+
+ expect(controller).to redirect_to(new_group_path)
+ expect(flash[:alert]).to eq('Specified URL cannot be used: "Only allowed schemes are http, https"')
+ end
+ end
+
+ context 'when local requests are allowed' do
+ %w[https://localhost:3000 http://192.168.0.1].each do |url|
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+
+ session[:bulk_import_gitlab_access_token] = 'test'
+ session[:bulk_import_gitlab_url] = url
+ end
+
+ it 'allows network request' do
+ get :status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ context 'when connection error occurs' do
+ before do
+ allow(controller).to receive(:client).and_return(client)
+ allow(client).to receive(:get).and_raise(Gitlab::BulkImport::Client::ConnectionError)
+ end
+
+ it 'returns 422' do
+ get :status, format: :json
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it 'clears session' do
+ get :status, format: :json
+
+ expect(session[:gitlab_url]).to be_nil
+ expect(session[:gitlab_access_token]).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'when gitlab_api_imports feature flag is disabled' do
+ before do
+ stub_feature_flags(bulk_import: false)
+ end
+
+ context 'POST configure' do
+ it 'returns 404' do
+ post :configure
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'GET status' do
+ it 'returns 404' do
+ get :status
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
+ context 'when user is signed out' do
+ before do
+ sign_out(user)
+ end
+
+ context 'POST configure' do
+ it 'redirects to sign in page' do
+ post :configure
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context 'GET status' do
+ it 'redirects to sign in page' do
+ get :status
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/import/manifest_controller_spec.rb b/spec/controllers/import/manifest_controller_spec.rb
index ec8bd45b65c..6b21b45e698 100644
--- a/spec/controllers/import/manifest_controller_spec.rb
+++ b/spec/controllers/import/manifest_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Import::ManifestController do
+RSpec.describe Import::ManifestController, :clean_gitlab_redis_shared_state do
include ImportSpecHelper
let_it_be(:user) { create(:user) }
@@ -16,42 +16,93 @@ RSpec.describe Import::ManifestController do
sign_in(user)
end
- def assign_session_group
- session[:manifest_import_repositories] = []
- session[:manifest_import_group_id] = group.id
+ describe 'POST upload' do
+ context 'with a valid manifest' do
+ it 'saves the manifest and redirects to the status page', :aggregate_failures do
+ post :upload, params: {
+ group_id: group.id,
+ manifest: fixture_file_upload('spec/fixtures/aosp_manifest.xml')
+ }
+
+ metadata = Gitlab::ManifestImport::Metadata.new(user)
+
+ expect(metadata.group_id).to eq(group.id)
+ expect(metadata.repositories.size).to eq(660)
+ expect(metadata.repositories.first).to include(name: 'platform/build', path: 'build/make')
+
+ expect(response).to redirect_to(status_import_manifest_path)
+ end
+ end
+
+ context 'with an invalid manifest' do
+ it 'displays an error' do
+ post :upload, params: {
+ group_id: group.id,
+ manifest: fixture_file_upload('spec/fixtures/invalid_manifest.xml')
+ }
+
+ expect(assigns(:errors)).to be_present
+ end
+ end
+
+ context 'when the user cannot create projects in the group' do
+ it 'displays an error' do
+ sign_in(create(:user))
+
+ post :upload, params: {
+ group_id: group.id,
+ manifest: fixture_file_upload('spec/fixtures/aosp_manifest.xml')
+ }
+
+ expect(assigns(:errors)).to be_present
+ end
+ end
end
describe 'GET status' do
- let(:repo1) { OpenStruct.new(id: 'test1', url: 'http://demo.host/test1') }
- let(:repo2) { OpenStruct.new(id: 'test2', url: 'http://demo.host/test2') }
+ let(:repo1) { { id: 'test1', url: 'http://demo.host/test1' } }
+ let(:repo2) { { id: 'test2', url: 'http://demo.host/test2' } }
let(:repos) { [repo1, repo2] }
- before do
- assign_session_group
+ shared_examples 'status action' do
+ it "returns variables for json request" do
+ project = create(:project, import_type: 'manifest', creator_id: user.id)
- session[:manifest_import_repositories] = repos
- end
+ get :status, format: :json
- it "returns variables for json request" do
- project = create(:project, import_type: 'manifest', creator_id: user.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos", 0, "id")).to eq(repo1[:id])
+ expect(json_response.dig("provider_repos", 1, "id")).to eq(repo2[:id])
+ expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
+ end
- get :status, format: :json
+ it "does not show already added project" do
+ project = create(:project, import_type: 'manifest', namespace: user.namespace, import_status: :finished, import_url: repo1[:url])
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos", 0, "id")).to eq(repo1.id)
- expect(json_response.dig("provider_repos", 1, "id")).to eq(repo2.id)
- expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
+ get :status, format: :json
+
+ expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
+ expect(json_response.dig("provider_repos").length).to eq(1)
+ expect(json_response.dig("provider_repos", 0, "id")).not_to eq(repo1[:id])
+ end
end
- it "does not show already added project" do
- project = create(:project, import_type: 'manifest', namespace: user.namespace, import_status: :finished, import_url: repo1.url)
+ context 'when the data is stored via Gitlab::ManifestImport::Metadata' do
+ before do
+ Gitlab::ManifestImport::Metadata.new(user).save(repos, group.id)
+ end
+
+ include_examples 'status action'
+ end
- get :status, format: :json
+ context 'when the data is stored in the user session' do
+ before do
+ session[:manifest_import_repositories] = repos
+ session[:manifest_import_group_id] = group.id
+ end
- expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
- expect(json_response.dig("provider_repos").length).to eq(1)
- expect(json_response.dig("provider_repos", 0, "id")).not_to eq(repo1.id)
+ include_examples 'status action'
end
end
end
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index a083cfac981..75a972d2f95 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -17,8 +17,53 @@ RSpec.describe InvitesController, :snowplow do
}
end
- before do
- controller.instance_variable_set(:@member, member)
+ shared_examples 'invalid token' do
+ context 'when invite token is not valid' do
+ let(:params) { { id: '_bogus_token_' } }
+
+ it 'renders the 404 page' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ shared_examples "tracks the 'accepted' event for the invitation reminders experiment" do
+ before do
+ stub_experiment(invitation_reminders: true)
+ allow(Gitlab::Experimentation).to receive(:enabled_for_attribute?).with(:invitation_reminders, member.invite_email).and_return(experimental_group)
+ end
+
+ context 'when in the control group' do
+ let(:experimental_group) { false }
+
+ it "tracks the 'accepted' event" do
+ request
+
+ expect_snowplow_event(
+ category: 'Growth::Acquisition::Experiment::InvitationReminders',
+ label: md5_member_global_id,
+ property: 'control_group',
+ action: 'accepted'
+ )
+ end
+ end
+
+ context 'when in the experimental group' do
+ let(:experimental_group) { true }
+
+ it "tracks the 'accepted' event" do
+ request
+
+ expect_snowplow_event(
+ category: 'Growth::Acquisition::Experiment::InvitationReminders',
+ label: md5_member_global_id,
+ property: 'experimental_group',
+ action: 'accepted'
+ )
+ end
+ end
end
describe 'GET #show' do
@@ -39,7 +84,7 @@ RSpec.describe InvitesController, :snowplow do
end
it 'forces re-confirmation if email does not match signed in user' do
- member.invite_email = 'bogus@email.com'
+ member.update!(invite_email: 'bogus@email.com')
expect do
request
@@ -64,8 +109,8 @@ RSpec.describe InvitesController, :snowplow do
it 'tracks the user as experiment group' do
request
- expect_snowplow_event(snowplow_event.merge(action: 'opened'))
- expect_snowplow_event(snowplow_event.merge(action: 'accepted'))
+ expect_snowplow_event(**snowplow_event.merge(action: 'opened'))
+ expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
@@ -76,10 +121,13 @@ RSpec.describe InvitesController, :snowplow do
it 'tracks the user as control group' do
request
- expect_snowplow_event(snowplow_event.merge(action: 'opened'))
- expect_snowplow_event(snowplow_event.merge(action: 'accepted'))
+ expect_snowplow_event(**snowplow_event.merge(action: 'opened'))
+ expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
+
+ it_behaves_like "tracks the 'accepted' event for the invitation reminders experiment"
+ it_behaves_like 'invalid token'
end
context 'when not logged in' do
@@ -125,7 +173,7 @@ RSpec.describe InvitesController, :snowplow do
it 'tracks the user as experiment group' do
request
- expect_snowplow_event(snowplow_event.merge(action: 'accepted'))
+ expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
@@ -136,8 +184,31 @@ RSpec.describe InvitesController, :snowplow do
it 'tracks the user as control group' do
request
- expect_snowplow_event(snowplow_event.merge(action: 'accepted'))
+ expect_snowplow_event(**snowplow_event.merge(action: 'accepted'))
end
end
+
+ it_behaves_like "tracks the 'accepted' event for the invitation reminders experiment"
+ it_behaves_like 'invalid token'
+ end
+
+ describe 'POST #decline for link in UI' do
+ before do
+ sign_in(user)
+ end
+
+ subject(:request) { post :decline, params: params }
+
+ it_behaves_like 'invalid token'
+ end
+
+ describe 'GET #decline for link in email' do
+ before do
+ sign_in(user)
+ end
+
+ subject(:request) { get :decline, params: params }
+
+ it_behaves_like 'invalid token'
end
end
diff --git a/spec/controllers/jira_connect/events_controller_spec.rb b/spec/controllers/jira_connect/events_controller_spec.rb
index d1a2dd6e7af..8a07f69e480 100644
--- a/spec/controllers/jira_connect/events_controller_spec.rb
+++ b/spec/controllers/jira_connect/events_controller_spec.rb
@@ -4,14 +4,20 @@ require 'spec_helper'
RSpec.describe JiraConnect::EventsController do
describe '#installed' do
- subject do
- post :installed, params: {
- clientKey: '1234',
- sharedSecret: 'secret',
+ let(:client_key) { '1234' }
+ let(:shared_secret) { 'secret' }
+ let(:params) do
+ {
+ clientKey: client_key,
+ sharedSecret: shared_secret,
baseUrl: 'https://test.atlassian.net'
}
end
+ subject do
+ post :installed, params: params
+ end
+
it 'saves the jira installation data' do
expect { subject }.to change { JiraConnectInstallation.count }.by(1)
end
@@ -19,15 +25,15 @@ RSpec.describe JiraConnect::EventsController do
it 'saves the correct values' do
subject
- installation = JiraConnectInstallation.find_by_client_key('1234')
+ installation = JiraConnectInstallation.find_by_client_key(client_key)
- expect(installation.shared_secret).to eq('secret')
+ expect(installation.shared_secret).to eq(shared_secret)
expect(installation.base_url).to eq('https://test.atlassian.net')
end
context 'client key already exists' do
it 'returns 422' do
- create(:jira_connect_installation, client_key: '1234')
+ create(:jira_connect_installation, client_key: client_key)
subject
@@ -35,6 +41,23 @@ RSpec.describe JiraConnect::EventsController do
end
end
+ context 'when it is a version update and shared_secret is not sent' do
+ let(:params) do
+ {
+ clientKey: client_key,
+ baseUrl: 'https://test.atlassian.net'
+ }
+ end
+
+ it 'validates the JWT token in authorization header and returns 200 without creating a new installation' do
+ create(:jira_connect_installation, client_key: client_key, shared_secret: shared_secret)
+ request.headers["Authorization"] = "Bearer #{Atlassian::Jwt.encode({ iss: client_key }, shared_secret)}"
+
+ expect { subject }.not_to change { JiraConnectInstallation.count }
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
describe '#uninstalled' do
let!(:installation) { create(:jira_connect_installation) }
let(:qsh) { Atlassian::Jwt.create_query_string_hash('https://gitlab.test/events/uninstalled', 'POST', 'https://gitlab.test') }
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index e08c92da87f..249e6322d1c 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -101,6 +101,19 @@ RSpec.describe ProfilesController, :request_store do
end
end
+ describe 'GET audit_log' do
+ it 'tracks search event', :snowplow do
+ sign_in(user)
+
+ get :audit_log
+
+ expect_snowplow_event(
+ category: 'ProfilesController',
+ action: 'search_audit_event'
+ )
+ end
+ end
+
describe 'PUT update_username' do
let(:namespace) { user.namespace }
let(:gitlab_shell) { Gitlab::Shell.new }
diff --git a/spec/controllers/projects/alert_management_controller_spec.rb b/spec/controllers/projects/alert_management_controller_spec.rb
index 6a1952f949b..d80147b5c59 100644
--- a/spec/controllers/projects/alert_management_controller_spec.rb
+++ b/spec/controllers/projects/alert_management_controller_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe Projects::AlertManagementController do
let(:role) { :reporter }
it 'shows 404' do
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ get :details, params: { namespace_id: project.namespace, project_id: project, id: id }
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index b998dee09b2..a56425c2a22 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -349,7 +349,7 @@ RSpec.describe Projects::BlobController do
end
it_behaves_like 'tracking unique hll events', :track_editor_edit_actions do
- subject { put :update, params: default_params, format: format }
+ subject(:request) { put :update, params: default_params }
let(:target_id) { 'g_edit_by_sfe' }
let(:expected_type) { instance_of(Integer) }
@@ -465,7 +465,7 @@ RSpec.describe Projects::BlobController do
end
it_behaves_like 'tracking unique hll events', :track_editor_edit_actions do
- subject { post :create, params: default_params, format: format }
+ subject(:request) { post :create, params: default_params }
let(:target_id) { 'g_edit_by_sfe' }
let(:expected_type) { instance_of(Integer) }
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 51a451570c5..52cd6869b04 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -251,6 +251,7 @@ RSpec.describe Projects::ClustersController do
cluster: {
name: 'new-cluster',
managed: '1',
+ namespace_per_environment: '0',
provider_gcp_attributes: {
gcp_project_id: 'gcp-project-12345',
legacy_abac: legacy_abac_param
@@ -278,6 +279,7 @@ RSpec.describe Projects::ClustersController do
expect(project.clusters.first).to be_kubernetes
expect(project.clusters.first.provider_gcp).to be_legacy_abac
expect(project.clusters.first.managed?).to be_truthy
+ expect(project.clusters.first.namespace_per_environment?).to be_falsy
end
context 'when legacy_abac param is false' do
@@ -369,6 +371,7 @@ RSpec.describe Projects::ClustersController do
expect(project.clusters.first).to be_user
expect(project.clusters.first).to be_kubernetes
+ expect(project.clusters.first).to be_namespace_per_environment
end
end
@@ -400,6 +403,7 @@ RSpec.describe Projects::ClustersController do
expect(cluster).to be_user
expect(cluster).to be_kubernetes
expect(cluster).to be_platform_kubernetes_rbac
+ expect(cluster).to be_namespace_per_environment
end
end
@@ -726,6 +730,7 @@ RSpec.describe Projects::ClustersController do
enabled: false,
name: 'my-new-cluster-name',
managed: false,
+ namespace_per_environment: false,
platform_kubernetes_attributes: {
namespace: 'my-namespace'
}
@@ -742,6 +747,7 @@ RSpec.describe Projects::ClustersController do
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
expect(cluster).not_to be_managed
+ expect(cluster).not_to be_namespace_per_environment
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
end
diff --git a/spec/controllers/projects/feature_flags_clients_controller_spec.rb b/spec/controllers/projects/feature_flags_clients_controller_spec.rb
new file mode 100644
index 00000000000..f527d2ba430
--- /dev/null
+++ b/spec/controllers/projects/feature_flags_clients_controller_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::FeatureFlagsClientsController do
+ include Gitlab::Routing
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ describe 'POST reset_token.json' do
+ subject(:reset_token) do
+ post :reset_token,
+ params: { namespace_id: project.namespace, project_id: project },
+ format: :json
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when user is a project maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'and feature flags client exist' do
+ it 'regenerates feature flags client token' do
+ project.create_operations_feature_flags_client!
+ expect { reset_token }.to change { project.reload.feature_flags_client_token }
+
+ expect(json_response['token']).to eq(project.feature_flags_client_token)
+ end
+ end
+
+ context 'but feature flags client does not exist' do
+ it 'returns 404' do
+ reset_token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'when user is not a project maintainer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 404' do
+ reset_token
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/feature_flags_controller_spec.rb b/spec/controllers/projects/feature_flags_controller_spec.rb
new file mode 100644
index 00000000000..96eeb6f239f
--- /dev/null
+++ b/spec/controllers/projects/feature_flags_controller_spec.rb
@@ -0,0 +1,1604 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::FeatureFlagsController do
+ include Gitlab::Routing
+ include FeatureFlagHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let(:user) { developer }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET index' do
+ render_views
+
+ subject { get(:index, params: view_params) }
+
+ context 'when there is no feature flags' do
+ it 'responds with success' do
+ is_expected.to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'for a list of feature flags' do
+ let!(:feature_flags) { create_list(:operations_feature_flag, 50, project: project) }
+
+ it 'responds with success' do
+ is_expected.to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the user is a reporter' do
+ let(:user) { reporter }
+
+ it 'responds with not found' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET #index.json' do
+ subject { get(:index, params: view_params, format: :json) }
+
+ let!(:feature_flag_active) do
+ create(:operations_feature_flag, project: project, active: true, name: 'feature_flag_a')
+ end
+
+ let!(:feature_flag_inactive) do
+ create(:operations_feature_flag, project: project, active: false, name: 'feature_flag_b')
+ end
+
+ it 'returns all feature flags as json response' do
+ subject
+
+ expect(json_response['feature_flags'].count).to eq(2)
+ expect(json_response['feature_flags'].first['name']).to eq(feature_flag_active.name)
+ expect(json_response['feature_flags'].second['name']).to eq(feature_flag_inactive.name)
+ end
+
+ it 'returns CRUD paths' do
+ subject
+
+ expected_edit_path = edit_project_feature_flag_path(project, feature_flag_active)
+ expected_update_path = project_feature_flag_path(project, feature_flag_active)
+ expected_destroy_path = project_feature_flag_path(project, feature_flag_active)
+
+ feature_flag_json = json_response['feature_flags'].first
+
+ expect(feature_flag_json['edit_path']).to eq(expected_edit_path)
+ expect(feature_flag_json['update_path']).to eq(expected_update_path)
+ expect(feature_flag_json['destroy_path']).to eq(expected_destroy_path)
+ end
+
+ it 'returns the summary of feature flags' do
+ subject
+
+ expect(json_response['count']['all']).to eq(2)
+ expect(json_response['count']['enabled']).to eq(1)
+ expect(json_response['count']['disabled']).to eq(1)
+ end
+
+ it 'matches json schema' do
+ is_expected.to match_response_schema('feature_flags')
+ end
+
+ it 'returns false for active when the feature flag is inactive even if it has an active scope' do
+ create(:operations_feature_flag_scope,
+ feature_flag: feature_flag_inactive,
+ environment_scope: 'production',
+ active: true)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ feature_flag_json = json_response['feature_flags'].second
+
+ expect(feature_flag_json['active']).to eq(false)
+ end
+
+ it 'returns the feature flag iid' do
+ subject
+
+ feature_flag_json = json_response['feature_flags'].first
+
+ expect(feature_flag_json['iid']).to eq(feature_flag_active.iid)
+ end
+
+ context 'when scope is specified' do
+ let(:view_params) do
+ { namespace_id: project.namespace, project_id: project, scope: scope }
+ end
+
+ context 'when all feature flags are requested' do
+ let(:scope) { 'all' }
+
+ it 'returns all feature flags' do
+ subject
+
+ expect(json_response['feature_flags'].count).to eq(2)
+ end
+ end
+
+ context 'when enabled feature flags are requested' do
+ let(:scope) { 'enabled' }
+
+ it 'returns enabled feature flags' do
+ subject
+
+ expect(json_response['feature_flags'].count).to eq(1)
+ expect(json_response['feature_flags'].first['active']).to be_truthy
+ end
+ end
+
+ context 'when disabled feature flags are requested' do
+ let(:scope) { 'disabled' }
+
+ it 'returns disabled feature flags' do
+ subject
+
+ expect(json_response['feature_flags'].count).to eq(1)
+ expect(json_response['feature_flags'].first['active']).to be_falsy
+ end
+ end
+ end
+
+ context 'when feature flags have additional scopes' do
+ let!(:feature_flag_active_scope) do
+ create(:operations_feature_flag_scope,
+ feature_flag: feature_flag_active,
+ environment_scope: 'production',
+ active: false)
+ end
+
+ let!(:feature_flag_inactive_scope) do
+ create(:operations_feature_flag_scope,
+ feature_flag: feature_flag_inactive,
+ environment_scope: 'staging',
+ active: false)
+ end
+
+ it 'returns a correct summary' do
+ subject
+
+ expect(json_response['count']['all']).to eq(2)
+ expect(json_response['count']['enabled']).to eq(1)
+ expect(json_response['count']['disabled']).to eq(1)
+ end
+
+ it 'recognizes feature flag 1 as active' do
+ subject
+
+ expect(json_response['feature_flags'].first['active']).to be_truthy
+ end
+
+ it 'recognizes feature flag 2 as inactive' do
+ subject
+
+ expect(json_response['feature_flags'].second['active']).to be_falsy
+ end
+
+ it 'has ordered scopes' do
+ subject
+
+ expect(json_response['feature_flags'][0]['scopes'][0]['id'])
+ .to be < json_response['feature_flags'][0]['scopes'][1]['id']
+ expect(json_response['feature_flags'][1]['scopes'][0]['id'])
+ .to be < json_response['feature_flags'][1]['scopes'][1]['id']
+ end
+
+ it 'does not have N+1 problem' do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+
+ related_count = recorded.log
+ .count { |query| query.include?('operations_feature_flag') }
+
+ expect(related_count).to be_within(5).of(2)
+ end
+ end
+
+ context 'with version 1 and 2 feature flags' do
+ let!(:new_version_feature_flag) do
+ create(:operations_feature_flag, :new_version_flag, project: project, name: 'feature_flag_c')
+ end
+
+ it 'returns all feature flags as json response' do
+ subject
+
+ expect(json_response['feature_flags'].count).to eq(3)
+ end
+
+ it 'returns only version 1 flags when new version flags are disabled' do
+ stub_feature_flags(feature_flags_new_version: false)
+
+ subject
+
+ expected = [feature_flag_active.name, feature_flag_inactive.name].sort
+ expect(json_response['feature_flags'].map { |f| f['name'] }.sort).to eq(expected)
+ end
+ end
+ end
+
+ describe 'GET new' do
+ render_views
+
+ subject { get(:new, params: view_params) }
+
+ it 'renders the form' do
+ is_expected.to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'GET #show.json' do
+ subject { get(:show, params: params, format: :json) }
+
+ let!(:feature_flag) do
+ create(:operations_feature_flag, project: project)
+ end
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid
+ }
+ end
+
+ it 'returns the feature flag as json response' do
+ subject
+
+ expect(json_response['name']).to eq(feature_flag.name)
+ expect(json_response['active']).to eq(feature_flag.active)
+ expect(json_response['version']).to eq('legacy_flag')
+ end
+
+ it 'matches json schema' do
+ is_expected.to match_response_schema('feature_flag')
+ end
+
+ it 'routes based on iid' do
+ other_project = create(:project)
+ other_project.add_developer(user)
+ other_feature_flag = create(:operations_feature_flag, project: other_project,
+ name: 'other_flag')
+ params = {
+ namespace_id: other_project.namespace,
+ project_id: other_project,
+ iid: other_feature_flag.iid
+ }
+
+ get(:show, params: params, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq(other_feature_flag.name)
+ end
+
+ it 'routes based on iid when new version flags are disabled' do
+ stub_feature_flags(feature_flags_new_version: false)
+ other_project = create(:project)
+ other_project.add_developer(user)
+ other_feature_flag = create(:operations_feature_flag, project: other_project,
+ name: 'other_flag')
+ params = {
+ namespace_id: other_project.namespace,
+ project_id: other_project,
+ iid: other_feature_flag.iid
+ }
+
+ get(:show, params: params, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq(other_feature_flag.name)
+ end
+
+ context 'when feature flag is not found' do
+ let!(:feature_flag) { }
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: 1
+ }
+ end
+
+ it 'returns 404' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user is reporter' do
+ let(:user) { reporter }
+
+ it 'returns 404' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when feature flags have additional scopes' do
+ context 'when there is at least one active scope' do
+ let!(:feature_flag) do
+ create(:operations_feature_flag, project: project, active: false)
+ end
+
+ let!(:feature_flag_scope_production) do
+ create(:operations_feature_flag_scope,
+ feature_flag: feature_flag,
+ environment_scope: 'review/*',
+ active: true)
+ end
+
+ it 'returns false for active' do
+ subject
+
+ expect(json_response['active']).to eq(false)
+ end
+ end
+
+ context 'when all scopes are inactive' do
+ let!(:feature_flag) do
+ create(:operations_feature_flag, project: project, active: false)
+ end
+
+ let!(:feature_flag_scope_production) do
+ create(:operations_feature_flag_scope,
+ feature_flag: feature_flag,
+ environment_scope: 'production',
+ active: false)
+ end
+
+ it 'recognizes the feature flag as inactive' do
+ subject
+
+ expect(json_response['active']).to be_falsy
+ end
+ end
+ end
+
+ context 'with a version 2 feature flag' do
+ let!(:new_version_feature_flag) do
+ create(:operations_feature_flag, :new_version_flag, project: project)
+ end
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: new_version_feature_flag.iid
+ }
+ end
+
+ it 'returns the feature flag' do
+ subject
+
+ expect(json_response['name']).to eq(new_version_feature_flag.name)
+ expect(json_response['active']).to eq(new_version_feature_flag.active)
+ expect(json_response['version']).to eq('new_version_flag')
+ end
+
+ it 'returns a 404 when new version flags are disabled' do
+ stub_feature_flags(feature_flags_new_version: false)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns strategies ordered by id' do
+ first_strategy = create(:operations_strategy, feature_flag: new_version_feature_flag)
+ second_strategy = create(:operations_strategy, feature_flag: new_version_feature_flag)
+
+ subject
+
+ expect(json_response['strategies'].map { |s| s['id'] }).to eq([first_strategy.id, second_strategy.id])
+ end
+ end
+ end
+
+ describe 'POST create.json' do
+ subject { post(:create, params: params, format: :json) }
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true
+ }
+ }
+ end
+
+ it 'returns 200' do
+ is_expected.to have_gitlab_http_status(:ok)
+ end
+
+ it 'creates a new feature flag' do
+ subject
+
+ expect(json_response['name']).to eq('my_feature_flag')
+ expect(json_response['active']).to be_truthy
+ end
+
+ it 'creates a default scope' do
+ subject
+
+ expect(json_response['scopes'].count).to eq(1)
+ expect(json_response['scopes'].first['environment_scope']).to eq('*')
+ expect(json_response['scopes'].first['active']).to be_truthy
+ end
+
+ it 'matches json schema' do
+ is_expected.to match_response_schema('feature_flag')
+ end
+
+ context 'when the same named feature flag has already existed' do
+ before do
+ create(:operations_feature_flag, name: 'my_feature_flag', project: project)
+ end
+
+ it 'returns 400' do
+ is_expected.to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns an error message' do
+ subject
+
+ expect(json_response['message']).to include('Name has already been taken')
+ end
+ end
+
+ context 'without the active parameter' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag'
+ }
+ }
+ end
+
+ it 'creates a flag with active set to true' do
+ expect { subject }.to change { Operations::FeatureFlag.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('feature_flag')
+ expect(json_response['active']).to eq(true)
+ expect(Operations::FeatureFlag.last.active).to eq(true)
+ end
+ end
+
+ context 'when user is reporter' do
+ let(:user) { reporter }
+
+ it 'returns 404' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when creates additional scope' do
+ let(:params) do
+ view_params.merge({
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ scopes_attributes: [{ environment_scope: '*', active: true },
+ { environment_scope: 'production', active: false }]
+ }
+ })
+ end
+
+ it 'creates feature flag scopes successfully' do
+ expect { subject }.to change { Operations::FeatureFlagScope.count }.by(2)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'creates feature flag scopes in a correct order' do
+ subject
+
+ expect(json_response['scopes'].first['environment_scope']).to eq('*')
+ expect(json_response['scopes'].second['environment_scope']).to eq('production')
+ end
+
+ context 'when default scope is not placed first' do
+ let(:params) do
+ view_params.merge({
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ scopes_attributes: [{ environment_scope: 'production', active: false },
+ { environment_scope: '*', active: true }]
+ }
+ })
+ end
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message'])
+ .to include('Default scope has to be the first element')
+ end
+ end
+ end
+
+ context 'when creates additional scope with a percentage rollout' do
+ it 'creates a strategy for the scope' do
+ params = view_params.merge({
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ scopes_attributes: [{ environment_scope: '*', active: true },
+ { environment_scope: 'production', active: false,
+ strategies: [{ name: 'gradualRolloutUserId',
+ parameters: { groupId: 'default', percentage: '42' } }] }]
+ }
+ })
+
+ post(:create, params: params, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ production_strategies_json = json_response['scopes'].second['strategies']
+ expect(production_strategies_json).to eq([{
+ 'name' => 'gradualRolloutUserId',
+ 'parameters' => { "groupId" => "default", "percentage" => "42" }
+ }])
+ end
+ end
+
+ context 'when creates additional scope with a userWithId strategy' do
+ it 'creates a strategy for the scope' do
+ params = view_params.merge({
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ scopes_attributes: [{ environment_scope: '*', active: true },
+ { environment_scope: 'production', active: false,
+ strategies: [{ name: 'userWithId',
+ parameters: { userIds: '123,4,6722' } }] }]
+ }
+ })
+
+ post(:create, params: params, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ production_strategies_json = json_response['scopes'].second['strategies']
+ expect(production_strategies_json).to eq([{
+ 'name' => 'userWithId',
+ 'parameters' => { "userIds" => "123,4,6722" }
+ }])
+ end
+ end
+
+ context 'when creates an additional scope without a strategy' do
+ it 'creates a default strategy' do
+ params = view_params.merge({
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ scopes_attributes: [{ environment_scope: '*', active: true }]
+ }
+ })
+
+ post(:create, params: params, format: :json)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ default_strategies_json = json_response['scopes'].first['strategies']
+ expect(default_strategies_json).to eq([{ "name" => "default", "parameters" => {} }])
+ end
+ end
+
+ context 'when creating a version 2 feature flag' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'new_version_flag'
+ }
+ }
+ end
+
+ it 'creates a new feature flag' do
+ subject
+
+ expect(json_response['name']).to eq('my_feature_flag')
+ expect(json_response['active']).to be_truthy
+ expect(json_response['version']).to eq('new_version_flag')
+ end
+ end
+
+ context 'when creating a version 2 feature flag with strategies and scopes' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'new_version_flag',
+ strategies_attributes: [{
+ name: 'userWithId',
+ parameters: { userIds: 'user1' },
+ scopes_attributes: [{ environment_scope: '*' }]
+ }]
+ }
+ }
+ end
+
+ it 'creates a new feature flag with the strategies and scopes' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('my_feature_flag')
+ expect(json_response['active']).to eq(true)
+ expect(json_response['strategies'].count).to eq(1)
+
+ strategy_json = json_response['strategies'].first
+ expect(strategy_json).to have_key('id')
+ expect(strategy_json['name']).to eq('userWithId')
+ expect(strategy_json['parameters']).to eq({ 'userIds' => 'user1' })
+ expect(strategy_json['scopes'].count).to eq(1)
+
+ scope_json = strategy_json['scopes'].first
+ expect(scope_json).to have_key('id')
+ expect(scope_json['environment_scope']).to eq('*')
+ end
+ end
+
+ context 'when creating a version 2 feature flag with a gradualRolloutUserId strategy' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'new_version_flag',
+ strategies_attributes: [{
+ name: 'gradualRolloutUserId',
+ parameters: { groupId: 'default', percentage: '15' },
+ scopes_attributes: [{ environment_scope: 'production' }]
+ }]
+ }
+ }
+ end
+
+ it 'creates the new strategy' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ strategy_json = json_response['strategies'].first
+ expect(strategy_json['name']).to eq('gradualRolloutUserId')
+ expect(strategy_json['parameters']).to eq({ 'groupId' => 'default', 'percentage' => '15' })
+ expect(strategy_json['scopes'].count).to eq(1)
+
+ scope_json = strategy_json['scopes'].first
+ expect(scope_json['environment_scope']).to eq('production')
+ end
+ end
+
+ context 'when creating a version 2 feature flag with a flexibleRollout strategy' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'new_version_flag',
+ strategies_attributes: [{
+ name: 'flexibleRollout',
+ parameters: { groupId: 'default', rollout: '15', stickiness: 'DEFAULT' },
+ scopes_attributes: [{ environment_scope: 'production' }]
+ }]
+ }
+ }
+ end
+
+ it 'creates the new strategy' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ strategy_json = json_response['strategies'].first
+ expect(strategy_json['name']).to eq('flexibleRollout')
+ expect(strategy_json['parameters']).to eq({ 'groupId' => 'default', 'rollout' => '15', 'stickiness' => 'DEFAULT' })
+ expect(strategy_json['scopes'].count).to eq(1)
+
+ scope_json = strategy_json['scopes'].first
+ expect(scope_json['environment_scope']).to eq('production')
+ end
+ end
+
+ context 'when creating a version 2 feature flag with a gitlabUserList strategy' do
+ let!(:user_list) do
+ create(:operations_feature_flag_user_list, project: project,
+ name: 'My List', user_xids: 'user1,user2')
+ end
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'new_version_flag',
+ strategies_attributes: [{
+ name: 'gitlabUserList',
+ parameters: {},
+ user_list_id: user_list.id,
+ scopes_attributes: [{ environment_scope: 'production' }]
+ }]
+ }
+ }
+ end
+
+ it 'creates the new strategy' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to match([a_hash_including({
+ 'name' => 'gitlabUserList',
+ 'parameters' => {},
+ 'user_list' => {
+ 'id' => user_list.id,
+ 'iid' => user_list.iid,
+ 'name' => 'My List',
+ 'user_xids' => 'user1,user2'
+ },
+ 'scopes' => [a_hash_including({
+ 'environment_scope' => 'production'
+ })]
+ })])
+ end
+ end
+
+ context 'when version parameter is invalid' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'bad_version'
+ }
+ }
+ end
+
+ it 'returns a 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to eq({ 'message' => 'Version is invalid' })
+ expect(Operations::FeatureFlag.count).to eq(0)
+ end
+ end
+
+ context 'when version 2 flags are disabled' do
+ context 'and attempting to create a version 2 flag' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true,
+ version: 'new_version_flag'
+ }
+ }
+ end
+
+ it 'returns a 400' do
+ stub_feature_flags(feature_flags_new_version: false)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(Operations::FeatureFlag.count).to eq(0)
+ end
+ end
+
+ context 'and attempting to create a version 1 flag' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ operations_feature_flag: {
+ name: 'my_feature_flag',
+ active: true
+ }
+ }
+ end
+
+ it 'creates the flag' do
+ stub_feature_flags(feature_flags_new_version: false)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Operations::FeatureFlag.count).to eq(1)
+ expect(json_response['version']).to eq('legacy_flag')
+ end
+ end
+ end
+ end
+
+ describe 'DELETE destroy.json' do
+ subject { delete(:destroy, params: params, format: :json) }
+
+ let!(:feature_flag) { create(:operations_feature_flag, project: project) }
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid
+ }
+ end
+
+ it 'returns 200' do
+ is_expected.to have_gitlab_http_status(:ok)
+ end
+
+ it 'deletes one feature flag' do
+ expect { subject }.to change { Operations::FeatureFlag.count }.by(-1)
+ end
+
+ it 'destroys the default scope' do
+ expect { subject }.to change { Operations::FeatureFlagScope.count }.by(-1)
+ end
+
+ it 'matches json schema' do
+ is_expected.to match_response_schema('feature_flag')
+ end
+
+ context 'when user is reporter' do
+ let(:user) { reporter }
+
+ it 'returns 404' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the feature flag does not exist' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: 0
+ }
+ end
+
+ it 'returns not found' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when there is an additional scope' do
+ let!(:scope) { create_scope(feature_flag, 'production', false) }
+
+ it 'destroys the default scope and production scope' do
+ expect { subject }.to change { Operations::FeatureFlagScope.count }.by(-2)
+ end
+ end
+
+ context 'with a version 2 flag' do
+ let!(:new_version_flag) { create(:operations_feature_flag, :new_version_flag, project: project) }
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: new_version_flag.iid
+ }
+ end
+
+ it 'deletes the flag' do
+ expect { subject }.to change { Operations::FeatureFlag.count }.by(-1)
+ end
+
+ context 'when new version flags are disabled' do
+ it 'returns a 404' do
+ stub_feature_flags(feature_flags_new_version: false)
+
+ expect { subject }.not_to change { Operations::FeatureFlag.count }
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
+ describe 'PUT update.json' do
+ def put_request(feature_flag, feature_flag_params)
+ params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: feature_flag_params
+ }
+
+ put(:update, params: params, format: :json, as: :json)
+ end
+
+ before do
+ stub_feature_flags(
+ feature_flags_legacy_read_only: false,
+ feature_flags_legacy_read_only_override: false
+ )
+ end
+
+ subject { put(:update, params: params, format: :json) }
+
+ let!(:feature_flag) do
+ create(:operations_feature_flag,
+ :legacy_flag,
+ name: 'ci_live_trace',
+ active: true,
+ project: project)
+ end
+
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ name: 'ci_new_live_trace'
+ }
+ }
+ end
+
+ it 'returns 200' do
+ is_expected.to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates the name of the feature flag name' do
+ subject
+
+ expect(json_response['name']).to eq('ci_new_live_trace')
+ end
+
+ it 'matches json schema' do
+ is_expected.to match_response_schema('feature_flag')
+ end
+
+ context 'when updates active' do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ active: false
+ }
+ }
+ end
+
+ it 'updates active from true to false' do
+ expect { subject }
+ .to change { feature_flag.reload.active }.from(true).to(false)
+ end
+
+ it "does not change default scope's active" do
+ expect { subject }
+ .not_to change { feature_flag.default_scope.reload.active }.from(true)
+ end
+
+ it 'updates active from false to true when an inactive feature flag has an active scope' do
+ feature_flag = create(:operations_feature_flag, project: project, name: 'my_flag', active: false)
+ create(:operations_feature_flag_scope, feature_flag: feature_flag, environment_scope: 'production', active: true)
+
+ put_request(feature_flag, { active: true })
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('feature_flag')
+ expect(json_response['active']).to eq(true)
+ expect(feature_flag.reload.active).to eq(true)
+ expect(feature_flag.default_scope.reload.active).to eq(false)
+ end
+ end
+
+ context 'when user is reporter' do
+ let(:user) { reporter }
+
+ it 'returns 404' do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context "when creates an additional scope for production environment" do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ scopes_attributes: [{ environment_scope: 'production', active: false }]
+ }
+ }
+ end
+
+ it 'creates a production scope' do
+ expect { subject }.to change { feature_flag.reload.scopes.count }.by(1)
+
+ expect(json_response['scopes'].last['environment_scope']).to eq('production')
+ expect(json_response['scopes'].last['active']).to be_falsy
+ end
+ end
+
+ context "when creates a default scope" do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ scopes_attributes: [{ environment_scope: '*', active: false }]
+ }
+ }
+ end
+
+ it 'returns 400' do
+ is_expected.to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context "when updates a default scope's active value" do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ scopes_attributes: [
+ {
+ id: feature_flag.default_scope.id,
+ environment_scope: '*',
+ active: false
+ }
+ ]
+ }
+ }
+ end
+
+ it "updates successfully" do
+ subject
+
+ expect(json_response['scopes'].first['environment_scope']).to eq('*')
+ expect(json_response['scopes'].first['active']).to be_falsy
+ end
+ end
+
+ context "when changes default scope's spec" do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ scopes_attributes: [
+ {
+ id: feature_flag.default_scope.id,
+ environment_scope: 'review/*'
+ }
+ ]
+ }
+ }
+ end
+
+ it 'returns 400' do
+ is_expected.to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context "when destroys the default scope" do
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ scopes_attributes: [
+ {
+ id: feature_flag.default_scope.id,
+ _destroy: 1
+ }
+ ]
+ }
+ }
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
+ end
+
+ context "when destroys a production scope" do
+ let!(:production_scope) { create_scope(feature_flag, 'production', true) }
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ iid: feature_flag.iid,
+ operations_feature_flag: {
+ scopes_attributes: [
+ {
+ id: production_scope.id,
+ _destroy: 1
+ }
+ ]
+ }
+ }
+ end
+
+ it 'destroys successfully' do
+ subject
+
+ scopes = json_response['scopes']
+ expect(scopes.any? { |scope| scope['environment_scope'] == 'production' })
+ .to be_falsy
+ end
+ end
+
+ describe "updating the strategy" do
+ it 'creates a default strategy' do
+ scope = create_scope(feature_flag, 'production', true, [])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: [{ name: 'default', parameters: {} }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([{
+ "name" => "default",
+ "parameters" => {}
+ }])
+ end
+
+ it 'creates a gradualRolloutUserId strategy' do
+ scope = create_scope(feature_flag, 'production', true, [])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: [{ name: 'gradualRolloutUserId',
+ parameters: { groupId: 'default', percentage: "70" } }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([{
+ "name" => "gradualRolloutUserId",
+ "parameters" => {
+ "groupId" => "default",
+ "percentage" => "70"
+ }
+ }])
+ end
+
+ it 'creates a userWithId strategy' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'default', parameters: {} }])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: [{ name: 'userWithId', parameters: { userIds: 'sam,fred' } }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([{
+ "name" => "userWithId",
+ "parameters" => { "userIds" => "sam,fred" }
+ }])
+ end
+
+ it 'updates an existing strategy' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'default', parameters: {} }])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: [{ name: 'gradualRolloutUserId',
+ parameters: { groupId: 'default', percentage: "50" } }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([{
+ "name" => "gradualRolloutUserId",
+ "parameters" => {
+ "groupId" => "default",
+ "percentage" => "50"
+ }
+ }])
+ end
+
+ it 'clears an existing strategy' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'default', parameters: {} }])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: []
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([])
+ end
+
+ it 'accepts multiple strategies' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'default', parameters: {} }])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: [
+ { name: 'gradualRolloutUserId', parameters: { groupId: 'mygroup', percentage: '55' } },
+ { name: 'userWithId', parameters: { userIds: 'joe' } }
+ ]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies'].length).to eq(2)
+ expect(scope_json['strategies']).to include({
+ "name" => "gradualRolloutUserId",
+ "parameters" => { "groupId" => "mygroup", "percentage" => "55" }
+ })
+ expect(scope_json['strategies']).to include({
+ "name" => "userWithId",
+ "parameters" => { "userIds" => "joe" }
+ })
+ end
+
+ it 'does not modify strategies when there is no strategies key in the params' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'default', parameters: {} }])
+
+ put_request(feature_flag, scopes_attributes: [{ id: scope.id }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([{
+ "name" => "default",
+ "parameters" => {}
+ }])
+ end
+
+ it 'leaves an existing strategy when there are no strategies in the params' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'gradualRolloutUserId',
+ parameters: { groupId: 'default', percentage: '10' } }])
+
+ put_request(feature_flag, scopes_attributes: [{ id: scope.id }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ scope_json = json_response['scopes'].find do |s|
+ s['environment_scope'] == 'production'
+ end
+ expect(scope_json['strategies']).to eq([{
+ "name" => "gradualRolloutUserId",
+ "parameters" => { "groupId" => "default", "percentage" => "10" }
+ }])
+ end
+
+ it 'does not accept extra parameters in the strategy params' do
+ scope = create_scope(feature_flag, 'production', true, [{ name: 'default', parameters: {} }])
+
+ put_request(feature_flag, scopes_attributes: [{
+ id: scope.id,
+ strategies: [{ name: 'userWithId', parameters: { userIds: 'joe', groupId: 'default' } }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq(["Scopes strategies parameters are invalid"])
+ end
+ end
+
+ context 'when legacy feature flags are set to be read only' do
+ it 'does not update the flag' do
+ stub_feature_flags(feature_flags_legacy_read_only: true)
+
+ put_request(feature_flag, name: 'ci_new_live_trace')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq(["Legacy feature flags are read-only"])
+ end
+
+ it 'updates the flag if the legacy read-only override is enabled for a particular project' do
+ stub_feature_flags(
+ feature_flags_legacy_read_only: true,
+ feature_flags_legacy_read_only_override: project
+ )
+
+ put_request(feature_flag, name: 'ci_new_live_trace')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('ci_new_live_trace')
+ end
+ end
+
+ context 'with a version 2 feature flag' do
+ let!(:new_version_flag) do
+ create(:operations_feature_flag,
+ :new_version_flag,
+ name: 'new-feature',
+ active: true,
+ project: project)
+ end
+
+ it 'creates a new strategy and scope' do
+ put_request(new_version_flag, strategies_attributes: [{
+ name: 'userWithId',
+ parameters: { userIds: 'user1' },
+ scopes_attributes: [{
+ environment_scope: 'production'
+ }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies'].count).to eq(1)
+ strategy_json = json_response['strategies'].first
+ expect(strategy_json['name']).to eq('userWithId')
+ expect(strategy_json['parameters']).to eq({
+ 'userIds' => 'user1'
+ })
+ expect(strategy_json['scopes'].count).to eq(1)
+ scope_json = strategy_json['scopes'].first
+ expect(scope_json['environment_scope']).to eq('production')
+ end
+
+ it 'creates a gradualRolloutUserId strategy' do
+ put_request(new_version_flag, strategies_attributes: [{
+ name: 'gradualRolloutUserId',
+ parameters: { groupId: 'default', percentage: '30' }
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies'].count).to eq(1)
+ strategy_json = json_response['strategies'].first
+ expect(strategy_json['name']).to eq('gradualRolloutUserId')
+ expect(strategy_json['parameters']).to eq({
+ 'groupId' => 'default',
+ 'percentage' => '30'
+ })
+ expect(strategy_json['scopes']).to eq([])
+ end
+
+ it 'creates a flexibleRollout strategy' do
+ put_request(new_version_flag, strategies_attributes: [{
+ name: 'flexibleRollout',
+ parameters: { groupId: 'default', rollout: '30', stickiness: 'DEFAULT' }
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies'].count).to eq(1)
+ strategy_json = json_response['strategies'].first
+ expect(strategy_json['name']).to eq('flexibleRollout')
+ expect(strategy_json['parameters']).to eq({
+ 'groupId' => 'default',
+ 'rollout' => '30',
+ 'stickiness' => 'DEFAULT'
+ })
+ expect(strategy_json['scopes']).to eq([])
+ end
+
+ it 'creates a gitlabUserList strategy' do
+ user_list = create(:operations_feature_flag_user_list, project: project, name: 'My List', user_xids: 'user1,user2')
+
+ put_request(new_version_flag, strategies_attributes: [{
+ name: 'gitlabUserList',
+ parameters: {},
+ user_list_id: user_list.id
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to match([a_hash_including({
+ 'id' => an_instance_of(Integer),
+ 'name' => 'gitlabUserList',
+ 'parameters' => {},
+ 'user_list' => {
+ 'id' => user_list.id,
+ 'iid' => user_list.iid,
+ 'name' => 'My List',
+ 'user_xids' => 'user1,user2'
+ },
+ 'scopes' => []
+ })])
+ end
+
+ it 'supports switching the associated user list for an existing gitlabUserList strategy' do
+ user_list = create(:operations_feature_flag_user_list, project: project, name: 'My List', user_xids: 'user1,user2')
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'gitlabUserList', parameters: {}, user_list: user_list)
+ other_user_list = create(:operations_feature_flag_user_list, project: project, name: 'Other List', user_xids: 'user3')
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ user_list_id: other_user_list.id
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to eq([{
+ 'id' => strategy.id,
+ 'name' => 'gitlabUserList',
+ 'parameters' => {},
+ 'user_list' => {
+ 'id' => other_user_list.id,
+ 'iid' => other_user_list.iid,
+ 'name' => 'Other List',
+ 'user_xids' => 'user3'
+ },
+ 'scopes' => []
+ }])
+ end
+
+ it 'automatically dissociates the user list when switching the type of an existing gitlabUserList strategy' do
+ user_list = create(:operations_feature_flag_user_list, project: project, name: 'My List', user_xids: 'user1,user2')
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'gitlabUserList', parameters: {}, user_list: user_list)
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ name: 'gradualRolloutUserId',
+ parameters: {
+ groupId: 'default',
+ percentage: '25'
+ }
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to eq([{
+ 'id' => strategy.id,
+ 'name' => 'gradualRolloutUserId',
+ 'parameters' => {
+ 'groupId' => 'default',
+ 'percentage' => '25'
+ },
+ 'scopes' => []
+ }])
+ end
+
+ it 'does not delete a user list when deleting a gitlabUserList strategy' do
+ user_list = create(:operations_feature_flag_user_list, project: project, name: 'My List', user_xids: 'user1,user2')
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'gitlabUserList', parameters: {}, user_list: user_list)
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ _destroy: true
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to eq([])
+ expect(::Operations::FeatureFlags::Strategy.count).to eq(0)
+ expect(::Operations::FeatureFlags::StrategyUserList.count).to eq(0)
+ expect(::Operations::FeatureFlags::UserList.first).to eq(user_list)
+ end
+
+ it 'returns not found when trying to create a gitlabUserList strategy with an invalid user list id' do
+ put_request(new_version_flag, strategies_attributes: [{
+ name: 'gitlabUserList',
+ parameters: {},
+ user_list_id: 1
+ }])
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'updates an existing strategy' do
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'default', parameters: {})
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ name: 'userWithId',
+ parameters: { userIds: 'user2,user3' }
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to eq([{
+ 'id' => strategy.id,
+ 'name' => 'userWithId',
+ 'parameters' => { 'userIds' => 'user2,user3' },
+ 'scopes' => []
+ }])
+ end
+
+ it 'updates an existing scope' do
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'default', parameters: {})
+ scope = create(:operations_scope, strategy: strategy, environment_scope: 'staging')
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ scopes_attributes: [{
+ id: scope.id,
+ environment_scope: 'sandbox'
+ }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies'].first['scopes']).to eq([{
+ 'id' => scope.id,
+ 'environment_scope' => 'sandbox'
+ }])
+ end
+
+ it 'deletes an existing strategy' do
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'default', parameters: {})
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ _destroy: true
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies']).to eq([])
+ end
+
+ it 'deletes an existing scope' do
+ strategy = create(:operations_strategy, feature_flag: new_version_flag, name: 'default', parameters: {})
+ scope = create(:operations_scope, strategy: strategy, environment_scope: 'staging')
+
+ put_request(new_version_flag, strategies_attributes: [{
+ id: strategy.id,
+ scopes_attributes: [{
+ id: scope.id,
+ _destroy: true
+ }]
+ }])
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['strategies'].first['scopes']).to eq([])
+ end
+
+ it 'does not update the flag if version 2 flags are disabled' do
+ stub_feature_flags(feature_flags_new_version: false)
+
+ put_request(new_version_flag, { name: 'some-other-name' })
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(new_version_flag.reload.name).to eq('new-feature')
+ end
+
+ it 'updates the flag when legacy feature flags are set to be read only' do
+ stub_feature_flags(feature_flags_legacy_read_only: true)
+
+ put_request(new_version_flag, name: 'some-other-name')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(new_version_flag.reload.name).to eq('some-other-name')
+ end
+ end
+ end
+
+ private
+
+ def view_params
+ { namespace_id: project.namespace, project_id: project }
+ end
+end
diff --git a/spec/controllers/projects/feature_flags_user_lists_controller_spec.rb b/spec/controllers/projects/feature_flags_user_lists_controller_spec.rb
new file mode 100644
index 00000000000..e0d1d3765b2
--- /dev/null
+++ b/spec/controllers/projects/feature_flags_user_lists_controller_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::FeatureFlagsUserListsController do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ before_all do
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ def request_params(extra_params = {})
+ { namespace_id: project.namespace, project_id: project }.merge(extra_params)
+ end
+
+ describe 'GET #new' do
+ it 'redirects when the user is unauthenticated' do
+ get(:new, params: request_params)
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+
+ it 'returns not found if the user does not belong to the project' do
+ user = create(:user)
+ sign_in(user)
+
+ get(:new, params: request_params)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found for a reporter' do
+ sign_in(reporter)
+
+ get(:new, params: request_params)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'renders the new page for a developer' do
+ sign_in(developer)
+
+ get(:new, params: request_params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'GET #edit' do
+ before do
+ sign_in(developer)
+ end
+
+ it 'renders the edit page for a developer' do
+ list = create(:operations_feature_flag_user_list, project: project)
+
+ get(:edit, params: request_params(iid: list.iid))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns not found with an iid that does not exist' do
+ list = create(:operations_feature_flag_user_list, project: project)
+
+ get(:edit, params: request_params(iid: list.iid + 1))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found for a list belonging to a another project' do
+ other_project = create(:project)
+ list = create(:operations_feature_flag_user_list, project: other_project)
+
+ get(:edit, params: request_params(iid: list.iid))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'GET #show' do
+ before do
+ sign_in(developer)
+ end
+
+ it 'renders the page for a developer' do
+ list = create(:operations_feature_flag_user_list, project: project)
+
+ get(:show, params: request_params(iid: list.iid))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns not found with an iid that does not exist' do
+ list = create(:operations_feature_flag_user_list, project: project)
+
+ get(:show, params: request_params(iid: list.iid + 1))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found for a list belonging to a another project' do
+ other_project = create(:project)
+ list = create(:operations_feature_flag_user_list, project: other_project)
+
+ get(:show, params: request_params(iid: list.iid))
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index 762ef795f6e..3baadde46dc 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
RSpec.describe Projects::GroupLinksController do
- let(:group) { create(:group, :private) }
- let(:group2) { create(:group, :private) }
- let(:project) { create(:project, :private, group: group2) }
- let(:user) { create(:user) }
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group2) { create(:group, :private) }
+ let_it_be(:project) { create(:project, :private, group: group2) }
+ let_it_be(:user) { create(:user) }
before do
project.add_maintainer(user)
@@ -142,4 +142,47 @@ RSpec.describe Projects::GroupLinksController do
end
end
end
+
+ describe '#update' do
+ let_it_be(:link) do
+ create(
+ :project_group_link,
+ {
+ project: project,
+ group: group
+ }
+ )
+ end
+
+ let(:expiry_date) { 1.month.from_now.to_date }
+
+ before do
+ travel_to Time.now.utc.beginning_of_day
+
+ put(
+ :update,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: link.id,
+ group_link: { group_access: Gitlab::Access::GUEST, expires_at: expiry_date }
+ },
+ format: :json
+ )
+ end
+
+ context 'when `expires_at` is set' do
+ it 'returns correct json response' do
+ expect(json_response).to eq({ "expires_in" => "about 1 month", "expires_soon" => false })
+ end
+ end
+
+ context 'when `expires_at` is not set' do
+ let(:expiry_date) { nil }
+
+ it 'returns empty json response' do
+ expect(json_response).to be_empty
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index bd543cebeec..b9c008d2950 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -48,6 +48,14 @@ RSpec.describe Projects::HooksController do
end
end
+ describe 'DELETE #destroy' do
+ let!(:hook) { create(:project_hook, project: project) }
+ let!(:log) { create(:web_hook_log, web_hook: hook) }
+ let(:params) { { namespace_id: project.namespace, project_id: project, id: hook } }
+
+ it_behaves_like 'Web hook destroyer'
+ end
+
describe '#test' do
let(:hook) { create(:project_hook, project: project) }
diff --git a/spec/controllers/projects/import/jira_controller_spec.rb b/spec/controllers/projects/import/jira_controller_spec.rb
index b82735a56b3..37a7fce0c23 100644
--- a/spec/controllers/projects/import/jira_controller_spec.rb
+++ b/spec/controllers/projects/import/jira_controller_spec.rb
@@ -12,7 +12,6 @@ RSpec.describe Projects::Import::JiraController do
def ensure_correct_config
sign_in(user)
project.add_maintainer(user)
- stub_feature_flags(jira_issue_import: true)
stub_jira_service_test
end
@@ -77,7 +76,6 @@ RSpec.describe Projects::Import::JiraController do
before do
sign_in(user)
project.add_maintainer(user)
- stub_feature_flags(jira_issue_import: true)
end
context 'when Jira service is not enabled for the project' do
diff --git a/spec/controllers/projects/incidents_controller_spec.rb b/spec/controllers/projects/incidents_controller_spec.rb
index 2baae0661cb..ddd15b9b1dd 100644
--- a/spec/controllers/projects/incidents_controller_spec.rb
+++ b/spec/controllers/projects/incidents_controller_spec.rb
@@ -3,44 +3,119 @@
require 'spec_helper'
RSpec.describe Projects::IncidentsController do
- let_it_be(:project) { create(:project) }
+ let_it_be_with_refind(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
let_it_be(:guest) { create(:user) }
+ let_it_be(:anonymous) { nil }
before_all do
- project.add_developer(developer)
project.add_guest(guest)
+ project.add_developer(developer)
+ end
+
+ before do
+ sign_in(user) if user
+ end
+
+ subject { make_request }
+
+ shared_examples 'not found' do
+ include_examples 'returning response status', :not_found
+ end
+
+ shared_examples 'login required' do
+ it 'redirects to the login page' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
end
describe 'GET #index' do
def make_request
- get :index, params: { namespace_id: project.namespace, project_id: project }
+ get :index, params: project_params
end
- it 'shows the page for user with developer role' do
- sign_in(developer)
- make_request
+ let(:user) { developer }
+
+ it 'shows the page' do
+ subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
end
context 'when user is unauthorized' do
- it 'redirects to the login page' do
- sign_out(developer)
- make_request
+ let(:user) { anonymous }
+
+ it_behaves_like 'login required'
+ end
+
+ context 'when user is a guest' do
+ let(:user) { guest }
+
+ it 'shows the page' do
+ subject
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
end
end
+ end
+
+ describe 'GET #show' do
+ def make_request
+ get :show, params: project_params(id: resource)
+ end
+
+ let_it_be(:resource) { create(:incident, project: project) }
+ let(:user) { developer }
+
+ it 'renders incident page' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+
+ expect(assigns(:incident)).to be_present
+ expect(assigns(:incident).author.association(:status)).to be_loaded
+ expect(assigns(:issue)).to be_present
+ expect(assigns(:noteable)).to eq(assigns(:incident))
+ end
+
+ context 'with non existing id' do
+ let(:resource) { non_existing_record_id }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'for issue' do
+ let_it_be(:resource) { create(:issue, project: project) }
+
+ it_behaves_like 'not found'
+ end
context 'when user is a guest' do
- it 'shows 404' do
- sign_in(guest)
- make_request
+ let(:user) { guest }
- expect(response).to have_gitlab_http_status(:not_found)
+ it 'shows the page' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
end
end
+
+ context 'when unauthorized' do
+ let(:user) { anonymous }
+
+ it_behaves_like 'login required'
+ end
+ end
+
+ private
+
+ def project_params(opts = {})
+ opts.reverse_merge(namespace_id: project.namespace, project_id: project)
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index ed5198bf015..f956baa0e22 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -388,15 +388,23 @@ RSpec.describe Projects::IssuesController do
# Rails router. A controller-style spec matches the wrong route, and
# session['user_return_to'] becomes incorrect.
describe 'Redirect after sign in', type: :request do
- context 'with an AJAX request' do
+ before_all do
+ project.add_developer(user)
+ end
+
+ before do
+ login_as(user)
+ end
+
+ context 'with a JSON request' do
it 'does not store the visited URL' do
- get project_issue_path(project, issue), xhr: true
+ get project_issue_path(project, issue, format: :json)
expect(session['user_return_to']).to be_blank
end
end
- context 'without an AJAX request' do
+ context 'with an HTML request' do
it 'stores the visited URL' do
get project_issue_path(project, issue)
@@ -1642,7 +1650,7 @@ RSpec.describe Projects::IssuesController do
end
it 'allows CSV export' do
- expect(ExportCsvWorker).to receive(:perform_async).with(viewer.id, project.id, anything)
+ expect(IssuableExportCsvWorker).to receive(:perform_async).with(:issue, viewer.id, project.id, anything)
request_csv
@@ -1657,7 +1665,7 @@ RSpec.describe Projects::IssuesController do
it 'redirects to the sign in page' do
request_csv
- expect(ExportCsvWorker).not_to receive(:perform_async)
+ expect(IssuableExportCsvWorker).not_to receive(:perform_async)
expect(response).to redirect_to(new_user_session_path)
end
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 94cce1964ca..80cb16966e5 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -121,13 +121,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:build).id).to eq(job.id)
end
-
- it 'has the correct build collection' do
- builds = assigns(:builds).map(&:id)
-
- expect(builds).to include(job.id, second_job.id)
- expect(builds).not_to include(third_job.id)
- end
end
context 'when job does not exist' do
@@ -204,16 +197,40 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with not expiry date' do
let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
- it 'exposes needed information' do
- get_show_json
+ context 'when artifacts are unlocked' do
+ before do
+ job.pipeline.unlocked!
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('job/job_details')
- expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
- expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
- expect(json_response['artifact']).not_to have_key('keep_path')
- expect(json_response['artifact']).not_to have_key('expired')
- expect(json_response['artifact']).not_to have_key('expired_at')
+ it 'exposes needed information' do
+ get_show_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
+ expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
+ expect(json_response['artifact']).not_to have_key('keep_path')
+ expect(json_response['artifact']).not_to have_key('expired')
+ expect(json_response['artifact']).not_to have_key('expired_at')
+ end
+ end
+
+ context 'when artifacts are locked' do
+ before do
+ job.pipeline.artifacts_locked!
+ end
+
+ it 'exposes needed information' do
+ get_show_json
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
+ expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
+ expect(json_response['artifact']).not_to have_key('keep_path')
+ expect(json_response['artifact']).not_to have_key('expired')
+ expect(json_response['artifact']).not_to have_key('expired_at')
+ end
end
end
@@ -740,19 +757,21 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
name: 'master', project: project)
sign_in(user)
-
- post_play
end
context 'when job is playable' do
let(:job) { create(:ci_build, :playable, pipeline: pipeline) }
it 'redirects to the played job page' do
+ post_play
+
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(namespace_project_job_path(id: job.id))
end
it 'transits to pending' do
+ post_play
+
expect(job.reload).to be_pending
end
@@ -760,15 +779,54 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:variable_attributes) { [{ key: 'first', secret_value: 'first' }] }
it 'assigns the job variables' do
+ post_play
+
expect(job.reload.job_variables.map(&:key)).to contain_exactly('first')
end
end
+
+ context 'when job is bridge' do
+ let(:downstream_project) { create(:project) }
+ let(:job) { create(:ci_bridge, :playable, pipeline: pipeline, downstream: downstream_project) }
+
+ before do
+ downstream_project.add_developer(user)
+ end
+
+ it 'redirects to the pipeline page' do
+ post_play
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(pipeline_path(pipeline))
+ builds_namespace_project_pipeline_path(id: pipeline.id)
+ end
+
+ it 'transits to pending' do
+ post_play
+
+ expect(job.reload).to be_pending
+ end
+
+ context 'when FF ci_manual_bridges is disabled' do
+ before do
+ stub_feature_flags(ci_manual_bridges: false)
+ end
+
+ it 'returns 404' do
+ post_play
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'when job is not playable' do
let(:job) { create(:ci_build, pipeline: pipeline) }
it 'renders unprocessable_entity' do
+ post_play
+
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index f213d104747..8a3c55033cb 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Projects::LabelsController do
- let(:group) { create(:group) }
- let(:project) { create(:project, namespace: group) }
- let(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, namespace: group) }
+ let_it_be(:user) { create(:user) }
before do
project.add_maintainer(user)
@@ -14,16 +14,21 @@ RSpec.describe Projects::LabelsController do
end
describe 'GET #index' do
- let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') }
- let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') }
- let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') }
- let!(:label_4) { create(:label, project: project, title: 'Label 4') }
- let!(:label_5) { create(:label, project: project, title: 'Label 5') }
-
- let!(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') }
- let!(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') }
- let!(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') }
- let!(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') }
+ let_it_be(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') }
+ let_it_be(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') }
+ let_it_be(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') }
+ let_it_be(:label_4) { create(:label, project: project, title: 'Label 4') }
+ let_it_be(:label_5) { create(:label, project: project, title: 'Label 5') }
+
+ let_it_be(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') }
+ let_it_be(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') }
+ let_it_be(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') }
+ let_it_be(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') }
+
+ let_it_be(:group_labels) { [group_label_3, group_label_4]}
+ let_it_be(:project_labels) { [label_4, label_5]}
+ let_it_be(:group_priority_labels) { [group_label_1, group_label_2]}
+ let_it_be(:project_priority_labels) { [label_1, label_2, label_3]}
before do
create(:label_priority, project: project, label: group_label_1, priority: 3)
@@ -68,6 +73,60 @@ RSpec.describe Projects::LabelsController do
end
end
+ context 'with subgroups' do
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') }
+ let_it_be(:subgroup_label_2) { create(:group_label, group: subgroup, title: 'subgroup_label_2') }
+
+ before do
+ project.update!(namespace: subgroup)
+ subgroup.add_owner(user)
+ create(:label_priority, project: project, label: subgroup_label_2, priority: 1)
+ end
+
+ RSpec.shared_examples 'returns ancestor group labels' do
+ it 'returns ancestor group labels', :aggregate_failures do
+ get :index, params: params
+
+ expect(assigns(:labels)).to match_array([subgroup_label_1] + group_labels + project_labels)
+ expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + group_priority_labels + project_priority_labels)
+ end
+ end
+
+ context 'when show_inherited_labels disabled' do
+ before do
+ stub_feature_flags(show_inherited_labels: false)
+ end
+
+ context 'when include_ancestor_groups false' do
+ let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
+
+ it 'does not return ancestor group labels', :aggregate_failures do
+ get :index, params: params
+
+ expect(assigns(:labels)).to match_array([subgroup_label_1] + project_labels)
+ expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + project_priority_labels)
+ end
+ end
+
+ context 'when include_ancestor_groups true' do
+ let(:params) { { namespace_id: project.namespace.to_param, project_id: project, include_ancestor_groups: true } }
+
+ it_behaves_like 'returns ancestor group labels'
+ end
+ end
+
+ context 'when show_inherited_labels enabled' do
+ let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
+
+ before do
+ stub_feature_flags(show_inherited_labels: true)
+ end
+
+ it_behaves_like 'returns ancestor group labels'
+ end
+ end
+
def list_labels
get :index, params: { namespace_id: project.namespace.to_param, project_id: project }
end
@@ -75,7 +134,7 @@ RSpec.describe Projects::LabelsController do
describe 'POST #generate' do
context 'personal project' do
- let(:personal_project) { create(:project, namespace: user.namespace) }
+ let_it_be(:personal_project) { create(:project, namespace: user.namespace) }
it 'creates labels' do
post :generate, params: { namespace_id: personal_project.namespace.to_param, project_id: personal_project }
@@ -116,8 +175,8 @@ RSpec.describe Projects::LabelsController do
end
describe 'POST #promote' do
- let!(:promoted_label_name) { "Promoted Label" }
- let!(:label_1) { create(:label, title: promoted_label_name, project: project) }
+ let_it_be(:promoted_label_name) { "Promoted Label" }
+ let_it_be(:label_1) { create(:label, title: promoted_label_name, project: project) }
context 'not group reporters' do
it 'denies access' do
@@ -196,7 +255,7 @@ RSpec.describe Projects::LabelsController do
end
context 'when requesting a redirected path' do
- let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
+ let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
it 'redirects to the canonical path' do
get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' }
@@ -242,7 +301,7 @@ RSpec.describe Projects::LabelsController do
end
context 'when requesting a redirected path' do
- let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
+ let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
it 'returns not found' do
post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' }
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
index 5f636bd4340..c2cc3d10ea0 100644
--- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -156,7 +156,7 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
expect(json_response).to include('old_path' => path,
'new_path' => path,
- 'blob_icon' => 'file-text-o',
+ 'blob_icon' => 'doc-text',
'blob_path' => a_string_ending_with(path),
'content' => content)
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index fa32d32f552..9e5d41b1075 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -17,7 +17,9 @@ RSpec.describe Projects::MilestonesController do
controller.instance_variable_set(:@project, project)
end
- it_behaves_like 'milestone tabs'
+ it_behaves_like 'milestone tabs' do
+ let(:request_params) { { namespace_id: project.namespace, project_id: project, id: milestone.iid } }
+ end
describe "#show" do
render_views
diff --git a/spec/controllers/projects/pipelines/stages_controller_spec.rb b/spec/controllers/projects/pipelines/stages_controller_spec.rb
index 6e8c08d95a1..a8b328c7563 100644
--- a/spec/controllers/projects/pipelines/stages_controller_spec.rb
+++ b/spec/controllers/projects/pipelines/stages_controller_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::Pipelines::StagesController do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
+ let(:downstream_project) { create(:project, :repository) }
before do
sign_in(user)
@@ -17,6 +18,7 @@ RSpec.describe Projects::Pipelines::StagesController do
before do
create_manual_build(pipeline, 'test', 'rspec 1/2')
create_manual_build(pipeline, 'test', 'rspec 2/2')
+ create_manual_bridge(pipeline, 'test', 'trigger')
pipeline.reload
end
@@ -32,6 +34,7 @@ RSpec.describe Projects::Pipelines::StagesController do
context 'when user has access' do
before do
project.add_maintainer(user)
+ downstream_project.add_maintainer(user)
end
context 'when the stage does not exists' do
@@ -46,12 +49,12 @@ RSpec.describe Projects::Pipelines::StagesController do
context 'when the stage exists' do
it 'starts all manual jobs' do
- expect(pipeline.builds.manual.count).to eq(2)
+ expect(pipeline.processables.manual.count).to eq(3)
play_manual_stage!
expect(response).to have_gitlab_http_status(:ok)
- expect(pipeline.builds.manual.count).to eq(0)
+ expect(pipeline.processables.manual.count).to eq(0)
end
end
end
@@ -68,5 +71,9 @@ RSpec.describe Projects::Pipelines::StagesController do
def create_manual_build(pipeline, stage, name)
create(:ci_build, :manual, pipeline: pipeline, stage: stage, name: name)
end
+
+ def create_manual_bridge(pipeline, stage, name)
+ create(:ci_bridge, :manual, pipeline: pipeline, stage: stage, name: name, downstream: downstream_project)
+ end
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index c3be7de25a8..0720124ea57 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -1148,4 +1148,84 @@ RSpec.describe Projects::PipelinesController do
}
end
end
+
+ describe 'GET config_variables.json' do
+ let(:result) { YAML.dump(ci_config) }
+
+ before do
+ stub_gitlab_ci_yml_for_sha(sha, result)
+ end
+
+ context 'when sending a valid sha' do
+ let(:sha) { 'master' }
+ let(:ci_config) do
+ {
+ variables: {
+ KEY1: { value: 'val 1', description: 'description 1' }
+ },
+ test: {
+ stage: 'test',
+ script: 'echo'
+ }
+ }
+ end
+
+ it 'returns variable list' do
+ get_config_variables
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['KEY1']).to eq({ 'value' => 'val 1', 'description' => 'description 1' })
+ end
+ end
+
+ context 'when sending an invalid sha' do
+ let(:sha) { 'invalid-sha' }
+ let(:ci_config) { nil }
+
+ it 'returns empty json' do
+ get_config_variables
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({})
+ end
+ end
+
+ context 'when sending an invalid config' do
+ let(:sha) { 'master' }
+ let(:ci_config) do
+ {
+ variables: {
+ KEY1: { value: 'val 1', description: 'description 1' }
+ },
+ test: {
+ stage: 'invalid',
+ script: 'echo'
+ }
+ }
+ end
+
+ it 'returns empty result' do
+ get_config_variables
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq({})
+ end
+ end
+
+ private
+
+ def stub_gitlab_ci_yml_for_sha(sha, result)
+ allow_any_instance_of(Repository)
+ .to receive(:gitlab_ci_yml_for)
+ .with(sha, '.gitlab-ci.yml')
+ .and_return(result)
+ end
+
+ def get_config_variables
+ get :config_variables, params: { namespace_id: project.namespace,
+ project_id: project,
+ sha: sha },
+ format: :json
+ end
+ end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index ae05e2d2631..74311fa89f3 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -228,6 +228,43 @@ RSpec.describe Projects::ProjectMembersController do
end
end
end
+
+ context 'expiration date' do
+ let(:expiry_date) { 1.month.from_now.to_date }
+
+ before do
+ travel_to Time.now.utc.beginning_of_day
+
+ put(
+ :update,
+ params: {
+ project_member: { expires_at: expiry_date },
+ namespace_id: project.namespace,
+ project_id: project,
+ id: requester
+ },
+ format: :json
+ )
+ end
+
+ context 'when `expires_at` is set' do
+ it 'returns correct json response' do
+ expect(json_response).to eq({
+ "expires_in" => "about 1 month",
+ "expires_soon" => false,
+ "expires_at_formatted" => expiry_date.to_time.in_time_zone.to_s(:medium)
+ })
+ end
+ end
+
+ context 'when `expires_at` is not set' do
+ let(:expiry_date) { nil }
+
+ it 'returns empty json response' do
+ expect(json_response).to be_empty
+ end
+ end
+ end
end
describe 'DELETE destroy' do
@@ -536,4 +573,19 @@ RSpec.describe Projects::ProjectMembersController do
end
end
end
+
+ describe 'POST resend_invite' do
+ let(:member) { create(:project_member, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ it 'is successful' do
+ post :resend_invite, params: { namespace_id: project.namespace, project_id: project, id: member }
+
+ expect(response).to have_gitlab_http_status(:found)
+ end
+ end
end
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index 6adee35b60a..59df9e78a3c 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe Projects::Registry::TagsController do
it 'tracks the event' do
expect_delete_tags(%w[test.])
- expect(controller).to receive(:track_event).with(:delete_tag)
+ expect(controller).to receive(:track_event).with(:delete_tag, {})
destroy_tag('test.')
end
diff --git a/spec/controllers/projects/releases/evidences_controller_spec.rb b/spec/controllers/projects/releases/evidences_controller_spec.rb
index d5a9665d6a5..0ec4cdf2a31 100644
--- a/spec/controllers/projects/releases/evidences_controller_spec.rb
+++ b/spec/controllers/projects/releases/evidences_controller_spec.rb
@@ -113,18 +113,6 @@ RSpec.describe Projects::Releases::EvidencesController do
it_behaves_like 'does not show the issue in evidence'
- context 'when the issue is confidential' do
- let(:issue) { create(:issue, :confidential, project: project) }
-
- it_behaves_like 'does not show the issue in evidence'
- end
-
- context 'when the user is the author of the confidential issue' do
- let(:issue) { create(:issue, :confidential, project: project, author: user) }
-
- it_behaves_like 'does not show the issue in evidence'
- end
-
context 'when project is private' do
let(:project) { create(:project, :repository, :private) }
@@ -143,32 +131,16 @@ RSpec.describe Projects::Releases::EvidencesController do
it_behaves_like 'does not show the issue in evidence'
- context 'when the issue is confidential' do
- let(:issue) { create(:issue, :confidential, project: project) }
-
- it_behaves_like 'does not show the issue in evidence'
- end
-
- context 'when the user is the author of the confidential issue' do
- let(:issue) { create(:issue, :confidential, project: project, author: user) }
-
- it_behaves_like 'does not show the issue in evidence'
- end
-
context 'when project is private' do
let(:project) { create(:project, :repository, :private) }
- it 'returns evidence ' do
- subject
-
- expect(json_response).to eq(evidence.summary)
- end
+ it_behaves_like 'does not show the issue in evidence'
end
context 'when project restricts the visibility of issues to project members only' do
let(:project) { create(:project, :repository, :issues_private) }
- it_behaves_like 'evidence not found'
+ it_behaves_like 'does not show the issue in evidence'
end
end
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 45beccfeef5..420d818daeb 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -194,14 +194,6 @@ RSpec.describe Projects::ReleasesController do
end
end
- context 'when feature flag `release_show_page` is disabled' do
- before do
- stub_feature_flags(release_show_page: false)
- end
-
- it_behaves_like 'not found'
- end
-
context 'when release does not exist' do
let(:tag) { 'non-existent-tag' }
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
index 66f20bd50c4..2443a823070 100644
--- a/spec/controllers/projects/runners_controller_spec.rb
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -73,4 +73,45 @@ RSpec.describe Projects::RunnersController do
expect(runner.active).to eq(false)
end
end
+
+ describe '#toggle_shared_runners' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ it 'toggles shared_runners_enabled when the group allows shared runners' do
+ project.update!(shared_runners_enabled: true)
+
+ post :toggle_shared_runners, params: params
+
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.shared_runners_enabled).to eq(false)
+ end
+
+ it 'toggles shared_runners_enabled when the group disallows shared runners but allows overrides' do
+ group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true)
+ project.update!(shared_runners_enabled: false)
+
+ post :toggle_shared_runners, params: params
+
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.shared_runners_enabled).to eq(true)
+ end
+
+ it 'does not enable if the group disallows shared runners' do
+ group.update!(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false)
+ project.update!(shared_runners_enabled: false)
+
+ post :toggle_shared_runners, params: params
+
+ project.reload
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(project.shared_runners_enabled).to eq(false)
+ expect(flash[:alert]).to eq("Cannot enable shared runners because parent group does not allow it")
+ end
+ end
end
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index 7f558ad9231..75135839a06 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -206,7 +206,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.5.0' do
before do
- prepare_knative_stubs(knative_05_service(knative_stub_options))
+ prepare_knative_stubs(knative_05_service(**knative_stub_options))
end
include_examples 'GET #show with valid data'
@@ -214,7 +214,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.6.0' do
before do
- prepare_knative_stubs(knative_06_service(knative_stub_options))
+ prepare_knative_stubs(knative_06_service(**knative_stub_options))
end
include_examples 'GET #show with valid data'
@@ -222,7 +222,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.7.0' do
before do
- prepare_knative_stubs(knative_07_service(knative_stub_options))
+ prepare_knative_stubs(knative_07_service(**knative_stub_options))
end
include_examples 'GET #show with valid data'
@@ -230,7 +230,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.9.0' do
before do
- prepare_knative_stubs(knative_09_service(knative_stub_options))
+ prepare_knative_stubs(knative_09_service(**knative_stub_options))
end
include_examples 'GET #show with valid data'
@@ -275,7 +275,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.5.0' do
before do
- prepare_knative_stubs(knative_05_service(knative_stub_options))
+ prepare_knative_stubs(knative_05_service(**knative_stub_options))
end
include_examples 'GET #index with data'
@@ -283,7 +283,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.6.0' do
before do
- prepare_knative_stubs(knative_06_service(knative_stub_options))
+ prepare_knative_stubs(knative_06_service(**knative_stub_options))
end
include_examples 'GET #index with data'
@@ -291,7 +291,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.7.0' do
before do
- prepare_knative_stubs(knative_07_service(knative_stub_options))
+ prepare_knative_stubs(knative_07_service(**knative_stub_options))
end
include_examples 'GET #index with data'
@@ -299,7 +299,7 @@ RSpec.describe Projects::Serverless::FunctionsController do
context 'on Knative 0.9.0' do
before do
- prepare_knative_stubs(knative_09_service(knative_stub_options))
+ prepare_knative_stubs(knative_09_service(**knative_stub_options))
end
include_examples 'GET #index with data'
diff --git a/spec/controllers/projects/settings/access_tokens_controller_spec.rb b/spec/controllers/projects/settings/access_tokens_controller_spec.rb
index 4743ab2b7c1..ff52b2a765a 100644
--- a/spec/controllers/projects/settings/access_tokens_controller_spec.rb
+++ b/spec/controllers/projects/settings/access_tokens_controller_spec.rb
@@ -5,27 +5,21 @@ require('spec_helper')
RSpec.describe Projects::Settings::AccessTokensController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
+ let_it_be(:bot_user) { create(:user, :project_bot) }
before_all do
project.add_maintainer(user)
+ project.add_maintainer(bot_user)
end
before do
sign_in(user)
end
- shared_examples 'feature unavailability' do
- context 'when flag is disabled' do
+ shared_examples 'feature unavailable' do
+ context 'user is not a maintainer' do
before do
- stub_feature_flags(resource_access_token: false)
- end
-
- it { is_expected.to have_gitlab_http_status(:not_found) }
- end
-
- context 'when environment is Gitlab.com' do
- before do
- allow(Gitlab).to receive(:com?).and_return(true)
+ project.add_developer(user)
end
it { is_expected.to have_gitlab_http_status(:not_found) }
@@ -35,156 +29,25 @@ RSpec.describe Projects::Settings::AccessTokensController do
describe '#index' do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
- it_behaves_like 'feature unavailability'
-
- context 'when feature is available' do
- let_it_be(:bot_user) { create(:user, :project_bot) }
- let_it_be(:active_project_access_token) { create(:personal_access_token, user: bot_user) }
- let_it_be(:inactive_project_access_token) { create(:personal_access_token, :revoked, user: bot_user) }
-
- before_all do
- project.add_maintainer(bot_user)
- end
-
- before do
- enable_feature
- end
-
- it 'retrieves active project access tokens' do
- subject
-
- expect(assigns(:active_project_access_tokens)).to contain_exactly(active_project_access_token)
- end
-
- it 'retrieves inactive project access tokens' do
- subject
-
- expect(assigns(:inactive_project_access_tokens)).to contain_exactly(inactive_project_access_token)
- end
-
- it 'lists all available scopes' do
- subject
-
- expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes)
- end
-
- it 'retrieves newly created personal access token value' do
- token_value = 'random-value'
- allow(PersonalAccessToken).to receive(:redis_getdel).with("#{user.id}:#{project.id}").and_return(token_value)
-
- subject
-
- expect(assigns(:new_project_access_token)).to eq(token_value)
- end
- end
+ it_behaves_like 'feature unavailable'
+ it_behaves_like 'project access tokens available #index'
end
- describe '#create', :clean_gitlab_redis_shared_state do
- subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
-
- let_it_be(:access_token_params) { {} }
-
- it_behaves_like 'feature unavailability'
-
- context 'when feature is available' do
- let_it_be(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: 1.month.since.to_date } }
-
- before do
- enable_feature
- end
-
- def created_token
- PersonalAccessToken.order(:created_at).last
- end
-
- it 'returns success message' do
- subject
-
- expect(response.flash[:notice]).to match(/\AYour new project access token has been created./i)
- end
-
- it 'creates project access token' do
- subject
-
- expect(created_token.name).to eq(access_token_params[:name])
- expect(created_token.scopes).to eq(access_token_params[:scopes])
- expect(created_token.expires_at).to eq(access_token_params[:expires_at])
- end
-
- it 'creates project bot user' do
- subject
-
- expect(created_token.user).to be_project_bot
- end
-
- it 'stores newly created token redis store' do
- expect(PersonalAccessToken).to receive(:redis_store!)
-
- subject
- end
+ describe '#create' do
+ let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: Date.today + 1.month } }
- it { expect { subject }.to change { User.count }.by(1) }
- it { expect { subject }.to change { PersonalAccessToken.count }.by(1) }
-
- context 'when unsuccessful' do
- before do
- allow_next_instance_of(ResourceAccessTokens::CreateService) do |service|
- allow(service).to receive(:execute).and_return ServiceResponse.error(message: 'Failed!')
- end
- end
+ subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
- it { expect(subject).to render_template(:index) }
- end
- end
+ it_behaves_like 'feature unavailable'
+ it_behaves_like 'project access tokens available #create'
end
- describe '#revoke' do
- subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } }
-
- let_it_be(:bot_user) { create(:user, :project_bot) }
- let_it_be(:project_access_token) { create(:personal_access_token, user: bot_user) }
-
- before_all do
- project.add_maintainer(bot_user)
- end
-
- it_behaves_like 'feature unavailability'
+ describe '#revoke', :sidekiq_inline do
+ let(:project_access_token) { create(:personal_access_token, user: bot_user) }
- context 'when feature is available' do
- before do
- enable_feature
- end
-
- it 'revokes token access' do
- subject
-
- expect(project_access_token.reload.revoked?).to be true
- end
-
- it 'removed membership of bot user' do
- subject
-
- expect(project.reload.bots).not_to include(bot_user)
- end
-
- it 'blocks project bot user' do
- subject
-
- expect(bot_user.reload.blocked?).to be true
- end
-
- it 'converts issuables of the bot user to ghost user' do
- issue = create(:issue, author: bot_user)
-
- subject
-
- expect(issue.reload.author.ghost?).to be true
- end
- end
- end
+ subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } }
- def enable_feature
- allow(Gitlab).to receive(:com?).and_return(false)
- stub_feature_flags(resource_access_token: true)
+ it_behaves_like 'feature unavailable'
+ it_behaves_like 'project access tokens available #revoke'
end
end
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 8498ff49826..7a6e11d53d4 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -230,6 +230,21 @@ RSpec.describe Projects::Settings::CiCdController do
end
end
+ context 'when forward_deployment_enabled is not specified' do
+ let(:params) { { ci_cd_settings_attributes: { forward_deployment_enabled: false } } }
+
+ before do
+ project.ci_cd_settings.update!(forward_deployment_enabled: nil)
+ end
+
+ it 'sets forward deployment enabled' do
+ subject
+
+ project.reload
+ expect(project.ci_forward_deployment_enabled).to eq(false)
+ end
+ end
+
context 'when max_artifacts_size is specified' do
let(:params) { { max_artifacts_size: 10 } }
@@ -266,4 +281,21 @@ RSpec.describe Projects::Settings::CiCdController do
end
end
end
+
+ describe 'GET #runner_setup_scripts' do
+ it 'renders the setup scripts' do
+ get :runner_setup_scripts, params: { os: 'linux', arch: 'amd64', namespace_id: project.namespace, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key("install")
+ expect(json_response).to have_key("register")
+ end
+
+ it 'renders errors if they occur' do
+ get :runner_setup_scripts, params: { os: 'foo', arch: 'bar', namespace_id: project.namespace, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to have_key("errors")
+ end
+ end
end
diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb
index ca1b0d2fe15..9fc9da1265e 100644
--- a/spec/controllers/projects/settings/operations_controller_spec.rb
+++ b/spec/controllers/projects/settings/operations_controller_spec.rb
@@ -6,9 +6,12 @@ RSpec.describe Projects::Settings::OperationsController do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) }
+ before_all do
+ project.add_maintainer(user)
+ end
+
before do
sign_in(user)
- project.add_maintainer(user)
end
shared_examples 'PATCHable' do
@@ -163,10 +166,6 @@ RSpec.describe Projects::Settings::OperationsController do
context 'updating each incident management setting' do
let(:new_incident_management_settings) { {} }
- before do
- project.add_maintainer(user)
- end
-
shared_examples 'a gitlab tracking event' do |params, event_key|
it "creates a gitlab tracking event #{event_key}" do
new_incident_management_settings = params
@@ -194,10 +193,6 @@ RSpec.describe Projects::Settings::OperationsController do
end
describe 'POST #reset_pagerduty_token' do
- before do
- project.add_maintainer(user)
- end
-
context 'with existing incident management setting has active PagerDuty webhook' do
let!(:incident_management_setting) do
create(:project_incident_management_setting, project: project, pagerduty_active: true)
@@ -392,10 +387,6 @@ RSpec.describe Projects::Settings::OperationsController do
end
describe 'POST #reset_alerting_token' do
- before do
- project.add_maintainer(user)
- end
-
context 'with existing alerting setting' do
let!(:alerting_setting) do
create(:project_alerting_setting, project: project)
@@ -478,6 +469,104 @@ RSpec.describe Projects::Settings::OperationsController do
end
end
+ context 'tracing integration' do
+ describe 'GET #show' do
+ context 'with existing setting' do
+ let_it_be(:setting) do
+ create(:project_tracing_setting, project: project)
+ end
+
+ it 'loads existing setting' do
+ get :show, params: project_params(project)
+
+ expect(controller.helpers.tracing_setting).to eq(setting)
+ end
+ end
+
+ context 'without an existing setting' do
+ it 'builds a new setting' do
+ get :show, params: project_params(project)
+
+ expect(controller.helpers.tracing_setting).to be_new_record
+ end
+ end
+ end
+
+ describe 'PATCH #update' do
+ let_it_be(:external_url) { 'https://gitlab.com' }
+ let(:params) do
+ {
+ tracing_setting_attributes: {
+ external_url: external_url
+ }
+ }
+ end
+
+ it_behaves_like 'PATCHable'
+
+ describe 'gitlab tracking', :snowplow do
+ shared_examples 'event tracking' do
+ it 'tracks an event' do
+ expect_snowplow_event(
+ category: 'project:operations:tracing',
+ action: 'external_url_populated'
+ )
+ end
+ end
+
+ shared_examples 'no event tracking' do
+ it 'does not track an event' do
+ expect_no_snowplow_event
+ end
+ end
+
+ before do
+ make_request
+ end
+
+ subject(:make_request) do
+ patch :update, params: project_params(project, params), format: :json
+ end
+
+ context 'without existing setting' do
+ context 'when creating a new setting' do
+ it_behaves_like 'event tracking'
+ end
+
+ context 'with invalid external_url' do
+ let_it_be(:external_url) { nil }
+
+ it_behaves_like 'no event tracking'
+ end
+ end
+
+ context 'with existing setting' do
+ let_it_be(:existing_setting) do
+ create(:project_tracing_setting,
+ project: project,
+ external_url: external_url)
+ end
+
+ context 'when changing external_url' do
+ let_it_be(:external_url) { 'https://example.com' }
+
+ it_behaves_like 'no event tracking'
+ end
+
+ context 'with unchanged external_url' do
+ it_behaves_like 'no event tracking'
+ end
+
+ context 'with invalid external_url' do
+ let_it_be(:external_url) { nil }
+
+ it_behaves_like 'no event tracking'
+ end
+ end
+ end
+ end
+ end
+
private
def project_params(project, params = {})
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index d0e412dfdb8..6b394fab14c 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -82,215 +82,6 @@ RSpec.describe Projects::SnippetsController do
end
end
- describe 'POST #create' do
- def create_snippet(project, snippet_params = {}, additional_params = {})
- sign_in(user)
-
- project.add_developer(user)
-
- post :create, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- project_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
- }.merge(additional_params)
-
- Snippet.last
- end
-
- it 'creates the snippet correctly' do
- snippet = create_snippet(project, visibility_level: Snippet::PRIVATE)
-
- expect(snippet.title).to eq('Title')
- expect(snippet.content).to eq('Content')
- expect(snippet.description).to eq('Description')
- end
-
- context 'when the snippet is spam' do
- before do
- allow_next_instance_of(Spam::AkismetService) do |instance|
- allow(instance).to receive(:spam?).and_return(true)
- end
- end
-
- context 'when the snippet is private' do
- it 'creates the snippet' do
- expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }
- .to change { Snippet.count }.by(1)
- end
- end
-
- context 'when the snippet is public' do
- it 'rejects the snippet' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }
- .not_to change { Snippet.count }
- expect(response).to render_template(:new)
- end
-
- it 'creates a spam log' do
- expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }
- .to log_spam(title: 'Title', user_id: user.id, noteable_type: 'ProjectSnippet')
- end
-
- it 'renders :new with reCAPTCHA disabled' do
- stub_application_setting(recaptcha_enabled: false)
-
- create_snippet(project, visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:new)
- end
-
- context 'reCAPTCHA enabled' do
- before do
- stub_application_setting(recaptcha_enabled: true)
- end
-
- it 'renders :verify with reCAPTCHA enabled' do
- create_snippet(project, visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:verify)
- end
-
- it 'renders snippet page when reCAPTCHA verified' do
- spammy_title = 'Whatever'
-
- spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
- create_snippet(project,
- { visibility_level: Snippet::PUBLIC },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
-
- expect(response).to redirect_to(project_snippet_path(project, Snippet.last))
- end
- end
- end
- end
- end
-
- describe 'PUT #update' do
- let(:visibility_level) { Snippet::PUBLIC }
- let(:snippet) { create :project_snippet, author: user, project: project, visibility_level: visibility_level }
-
- def update_snippet(snippet_params = {}, additional_params = {})
- sign_in(user)
-
- project.add_developer(user)
-
- put :update, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: snippet,
- project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
- }.merge(additional_params)
-
- snippet.reload
- end
-
- context 'when the snippet is spam' do
- before do
- allow_next_instance_of(Spam::AkismetService) do |instance|
- allow(instance).to receive(:spam?).and_return(true)
- end
- end
-
- context 'when the snippet is private' do
- let(:visibility_level) { Snippet::PRIVATE }
-
- it 'updates the snippet' do
- expect { update_snippet(title: 'Foo') }
- .to change { snippet.reload.title }.to('Foo')
- end
- end
-
- context 'when the snippet is public' do
- it 'rejects the snippet' do
- expect { update_snippet(title: 'Foo') }
- .not_to change { snippet.reload.title }
- end
-
- it 'creates a spam log' do
- expect { update_snippet(title: 'Foo') }
- .to log_spam(title: 'Foo', user_id: user.id, noteable_type: 'ProjectSnippet')
- end
-
- it 'renders :edit with reCAPTCHA disabled' do
- stub_application_setting(recaptcha_enabled: false)
-
- update_snippet(title: 'Foo')
-
- expect(response).to render_template(:edit)
- end
-
- context 'reCAPTCHA enabled' do
- before do
- stub_application_setting(recaptcha_enabled: true)
- end
-
- it 'renders :verify with reCAPTCHA enabled' do
- update_snippet(title: 'Foo')
-
- expect(response).to render_template(:verify)
- end
-
- it 'renders snippet page when reCAPTCHA verified' do
- spammy_title = 'Whatever'
-
- spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
- snippet = update_snippet({ title: spammy_title },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
-
- expect(response).to redirect_to(project_snippet_path(project, snippet))
- end
- end
- end
-
- context 'when the private snippet is made public' do
- let(:visibility_level) { Snippet::PRIVATE }
-
- it 'rejects the snippet' do
- expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
- .not_to change { snippet.reload.title }
- end
-
- it 'creates a spam log' do
- expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
- .to log_spam(title: 'Foo', user_id: user.id, noteable_type: 'ProjectSnippet')
- end
-
- it 'renders :edit with reCAPTCHA disabled' do
- stub_application_setting(recaptcha_enabled: false)
-
- update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:edit)
- end
-
- context 'reCAPTCHA enabled' do
- before do
- stub_application_setting(recaptcha_enabled: true)
- end
-
- it 'renders :verify' do
- update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:verify)
- end
-
- it 'renders snippet page' do
- spammy_title = 'Whatever'
-
- spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
- snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
-
- expect(response).to redirect_to(project_snippet_path(project, snippet))
- end
- end
- end
- end
- end
-
describe 'POST #mark_as_spam' do
let_it_be(:snippet) { create(:project_snippet, :private, project: project, author: user) }
@@ -329,12 +120,6 @@ RSpec.describe Projects::SnippetsController do
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_gitlab_http_status(:ok)
end
-
- it 'renders the blob from the repository' do
- subject
-
- expect(assigns(:blob)).to eq(project_snippet.blobs.first)
- end
end
%w[show raw].each do |action|
@@ -395,6 +180,16 @@ RSpec.describe Projects::SnippetsController do
end
end
+ describe 'GET #show as JSON' do
+ it 'renders the blob from the repository' do
+ project_snippet = create(:project_snippet, :public, :repository, project: project, author: user)
+
+ get :show, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }, format: :json
+
+ expect(assigns(:blob)).to eq(project_snippet.blobs.first)
+ end
+ end
+
describe "GET #show for embeddable content" do
let(:project_snippet) { create(:project_snippet, :repository, snippet_permission, project: project, author: user) }
let(:extra_params) { {} }
@@ -533,62 +328,4 @@ RSpec.describe Projects::SnippetsController do
it_behaves_like 'content disposition headers'
end
end
-
- describe 'DELETE #destroy' do
- let_it_be(:snippet) { create(:project_snippet, :private, project: project, author: user) }
-
- let(:params) do
- {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: snippet.to_param
- }
- end
-
- subject { delete :destroy, params: params }
-
- context 'when current user has ability to destroy the snippet' do
- before do
- sign_in(user)
- end
-
- it 'removes the snippet' do
- subject
-
- expect { snippet.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- context 'when snippet is succesfuly destroyed' do
- it 'redirects to the project snippets page' do
- subject
-
- expect(response).to redirect_to(project_snippets_path(project))
- end
- end
-
- context 'when snippet is not destroyed' do
- before do
- allow(snippet).to receive(:destroy).and_return(false)
- controller.instance_variable_set(:@snippet, snippet)
- end
-
- it 'renders the snippet page with errors' do
- subject
-
- expect(flash[:alert]).to eq('Failed to remove snippet.')
- expect(response).to redirect_to(project_snippet_path(project, snippet))
- end
- end
- end
-
- context 'when current_user does not have ability to destroy the snippet' do
- it 'responds with status 404' do
- sign_in(other_user)
-
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
end
diff --git a/spec/controllers/projects/static_site_editor_controller_spec.rb b/spec/controllers/projects/static_site_editor_controller_spec.rb
index 7883c7e6f81..6ea730cbf27 100644
--- a/spec/controllers/projects/static_site_editor_controller_spec.rb
+++ b/spec/controllers/projects/static_site_editor_controller_spec.rb
@@ -5,9 +5,11 @@ require 'spec_helper'
RSpec.describe Projects::StaticSiteEditorController do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { create(:user) }
- let(:data) { instance_double(Hash) }
+ let(:data) { { key: 'value' } }
describe 'GET show' do
+ render_views
+
let(:default_params) do
{
namespace_id: project.namespace,
@@ -50,41 +52,83 @@ RSpec.describe Projects::StaticSiteEditorController do
end
end
- %w[developer maintainer].each do |role|
- context "as #{role}" do
- before_all do
- project.add_role(user, role)
+ context "as developer" do
+ before do
+ allow(Gitlab::UsageDataCounters::StaticSiteEditorCounter).to receive(:increment_views_count)
+ project.add_role(user, 'developer')
+ sign_in(user)
+ get :show, params: default_params
+ end
+
+ it 'increases the views counter' do
+ expect(Gitlab::UsageDataCounters::StaticSiteEditorCounter).to have_received(:increment_views_count)
+ end
+
+ it 'renders the edit page' do
+ expect(response).to render_template(:show)
+ end
+
+ it 'assigns ref and path variables' do
+ expect(assigns(:ref)).to eq('master')
+ expect(assigns(:path)).to eq('README.md')
+ end
+
+ context 'when combination of ref and path is incorrect' do
+ let(:default_params) { super().merge(id: 'unknown') }
+
+ it 'responds with 404 page' do
+ expect(response).to have_gitlab_http_status(:not_found)
end
+ end
+
+ context 'when invalid config file' do
+ let(:service_response) { ServiceResponse.error(message: 'invalid') }
- before do
- sign_in(user)
- get :show, params: default_params
+ it 'redirects to project page and flashes error message' do
+ expect(response).to redirect_to(project_path(project))
+ expect(response).to set_flash[:alert].to('invalid')
end
+ end
- it 'renders the edit page' do
- expect(response).to render_template(:show)
+ context 'with a service response payload containing multiple data types' do
+ let(:data) do
+ {
+ a_string: 'string',
+ an_array: [
+ {
+ foo: 'bar'
+ }
+ ],
+ an_integer: 123,
+ a_hash: {
+ a_deeper_hash: {
+ foo: 'bar'
+ }
+ },
+ a_boolean: true
+ }
end
- it 'assigns a required variables' do
- expect(assigns(:data)).to eq(data)
- expect(assigns(:ref)).to eq('master')
- expect(assigns(:path)).to eq('README.md')
+ let(:assigns_data) { assigns(:data) }
+
+ it 'leaves data values which are strings as strings' do
+ expect(assigns_data[:a_string]).to eq('string')
end
- context 'when combination of ref and path is incorrect' do
- let(:default_params) { super().merge(id: 'unknown') }
+ it 'leaves data values which are integers as integers' do
+ expect(assigns_data[:an_integer]).to eq(123)
+ end
- it 'responds with 404 page' do
- expect(response).to have_gitlab_http_status(:not_found)
- end
+ it 'serializes data values which are booleans to JSON' do
+ expect(assigns_data[:a_boolean]).to eq('true')
end
- context 'when invalid config file' do
- let(:service_response) { ServiceResponse.error(message: 'invalid') }
+ it 'serializes data values which are arrays to JSON' do
+ expect(assigns_data[:an_array]).to eq('[{"foo":"bar"}]')
+ end
- it 'returns 422' do
- expect(response).to have_gitlab_http_status(:unprocessable_entity)
- end
+ it 'serializes data values which are hashes to JSON' do
+ expect(assigns_data[:a_hash]).to eq('{"a_deeper_hash":{"foo":"bar"}}')
end
end
end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index d213d003bed..57760088183 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -131,4 +131,25 @@ RSpec.describe Projects::TagsController do
end
end
end
+
+ describe 'DELETE #destroy' do
+ let(:tag) { project.repository.add_tag(user, 'fake-tag', 'master') }
+ let(:request) do
+ delete(:destroy, params: { id: tag.name, namespace_id: project.namespace.to_param, project_id: project })
+ end
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it 'deletes tag' do
+ request
+
+ expect(response).to be_successful
+ expect(response.body).to include("Tag was removed")
+
+ expect(project.repository.find_tag(tag.name)).not_to be_present
+ end
+ end
end
diff --git a/spec/controllers/projects/tracings_controller_spec.rb b/spec/controllers/projects/tracings_controller_spec.rb
new file mode 100644
index 00000000000..1f8a68cc861
--- /dev/null
+++ b/spec/controllers/projects/tracings_controller_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::TracingsController do
+ let_it_be(:user) { create(:user) }
+
+ describe 'GET show' do
+ shared_examples 'user with read access' do |visibility_level|
+ let(:project) { create(:project, visibility_level) }
+
+ %w[developer maintainer].each do |role|
+ context "with a #{visibility_level} project and #{role} role" do
+ before do
+ project.add_role(user, role)
+ end
+
+ it 'renders OK' do
+ get :show, params: { namespace_id: project.namespace, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ end
+ end
+ end
+ end
+
+ shared_examples 'user without read access' do |visibility_level|
+ let(:project) { create(:project, visibility_level) }
+
+ %w[guest reporter].each do |role|
+ context "with a #{visibility_level} project and #{role} role" do
+ before do
+ project.add_role(user, role)
+ end
+
+ it 'returns 404' do
+ get :show, params: { namespace_id: project.namespace, project_id: project }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ context 'with maintainer role' do
+ it_behaves_like 'user with read access', :public
+ it_behaves_like 'user with read access', :internal
+ it_behaves_like 'user with read access', :private
+ end
+
+ context 'without maintainer role' do
+ it_behaves_like 'user without read access', :public
+ it_behaves_like 'user without read access', :internal
+ it_behaves_like 'user without read access', :private
+ end
+ end
+end
diff --git a/spec/controllers/projects/web_ide_terminals_controller_spec.rb b/spec/controllers/projects/web_ide_terminals_controller_spec.rb
index 3eb3d5da351..09c471d2885 100644
--- a/spec/controllers/projects/web_ide_terminals_controller_spec.rb
+++ b/spec/controllers/projects/web_ide_terminals_controller_spec.rb
@@ -9,17 +9,20 @@ RSpec.describe Projects::WebIdeTerminalsController do
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
- let_it_be(:project) { create(:project, :private, :repository, namespace: owner.namespace) }
+ let_it_be(:project) do
+ create(:project, :private, :repository, namespace: owner.namespace).tap do |project|
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ end
+ end
+
let(:pipeline) { create(:ci_pipeline, project: project, source: :webide, config_source: :webide_source, user: user) }
let(:job) { create(:ci_build, pipeline: pipeline, user: user, project: project) }
let(:user) { maintainer }
before do
- project.add_maintainer(maintainer)
- project.add_developer(developer)
- project.add_reporter(reporter)
- project.add_guest(guest)
-
sign_in(user)
end
@@ -158,11 +161,11 @@ RSpec.describe Projects::WebIdeTerminalsController do
end
context 'access rights' do
- before do
- subject
+ it_behaves_like 'terminal access rights' do
+ before do
+ subject
+ end
end
-
- it_behaves_like 'terminal access rights'
end
it 'increases the web ide terminal counter' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index e4374a8f104..0640f9e5724 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -213,13 +213,13 @@ RSpec.describe ProjectsController do
expect(assigns(:issuable_meta_data)).not_to be_nil
end
- it 'shows customize workflow page if wiki and issues are disabled' do
+ it 'shows activity page if wiki and issues are disabled' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
get :show, params: { namespace_id: project.namespace, id: project }
- expect(response).to render_template("projects/_customize_workflow")
+ expect(response).to render_template("projects/_activity")
end
it 'shows activity if enabled by user' do
diff --git a/spec/controllers/registrations/experience_levels_controller_spec.rb b/spec/controllers/registrations/experience_levels_controller_spec.rb
index ee1acf3d93d..4be67f29107 100644
--- a/spec/controllers/registrations/experience_levels_controller_spec.rb
+++ b/spec/controllers/registrations/experience_levels_controller_spec.rb
@@ -85,16 +85,49 @@ RSpec.describe Registrations::ExperienceLevelsController do
end
end
- context 'when a namespace_path is sent' do
- it { is_expected.to have_gitlab_http_status(:redirect) }
- it { is_expected.to redirect_to(group_path(namespace)) }
- end
+ describe 'redirection' do
+ let(:project) { build(:project, namespace: namespace, creator: user, path: 'project-path') }
+ let(:issues_board) { build(:board, id: 123, project: project) }
+
+ before do
+ stub_experiment_for_user(
+ onboarding_issues: true,
+ default_to_issues_board: default_to_issues_board_xp?
+ )
+ allow_next_instance_of(LearnGitlab) do |learn_gitlab|
+ allow(learn_gitlab).to receive(:available?).and_return(learn_gitlab_available?)
+ allow(learn_gitlab).to receive(:project).and_return(project)
+ allow(learn_gitlab).to receive(:board).and_return(issues_board)
+ end
+ end
+
+ context 'when namespace_path param is missing' do
+ let(:params) { super().merge(namespace_path: nil) }
+
+ where(
+ default_to_issues_board_xp?: [true, false],
+ learn_gitlab_available?: [true, false]
+ )
- context 'when no namespace_path is sent' do
- let(:params) { super().merge(namespace_path: nil) }
+ with_them do
+ it { is_expected.to redirect_to('/') }
+ end
+ end
- it { is_expected.to have_gitlab_http_status(:redirect) }
- it { is_expected.to redirect_to(root_path) }
+ context 'when we have a namespace_path param' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:default_to_issues_board_xp?, :learn_gitlab_available?, :path) do
+ true | true | '/group-path/project-path/-/boards/123'
+ true | false | '/group-path'
+ false | true | '/group-path'
+ false | false | '/group-path'
+ end
+
+ with_them do
+ it { is_expected.to redirect_to(path) }
+ end
+ end
end
describe 'applying the chosen level' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 60957dc72e6..501d8d4a78d 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -37,65 +37,123 @@ RSpec.describe RegistrationsController do
expect(response).to redirect_to(new_user_session_path(anchor: 'register-pane'))
end
end
+ end
- context 'with sign up flow and terms_opt_in experiment being enabled' do
- before do
- stub_experiment(signup_flow: true, terms_opt_in: true)
- end
+ describe '#create' do
+ let(:base_user_params) { { first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: 'Any_password' } }
+ let(:user_params) { { user: base_user_params } }
+
+ subject { post(:create, params: user_params) }
- context 'when user is not part of the experiment' do
+ context '`blocked_pending_approval` state' do
+ context 'when the feature is enabled' do
before do
- stub_experiment_for_user(signup_flow: true, terms_opt_in: false)
+ stub_feature_flags(admin_approval_for_new_user_signups: true)
end
- it 'tracks event with right parameters' do
- expect(Gitlab::Tracking).to receive(:event).with(
- 'Growth::Acquisition::Experiment::TermsOptIn',
- 'start',
- label: anything,
- property: 'control_group'
- )
+ context 'when the `require_admin_approval_after_user_signup` setting is turned on' do
+ before do
+ stub_application_setting(require_admin_approval_after_user_signup: true)
+ end
- subject
+ it 'signs up the user in `blocked_pending_approval` state' do
+ subject
+ created_user = User.find_by(email: 'new@user.com')
+
+ expect(created_user).to be_present
+ expect(created_user.blocked_pending_approval?).to eq(true)
+ end
+
+ it 'does not log in the user after sign up' do
+ subject
+
+ expect(controller.current_user).to be_nil
+ end
+
+ it 'shows flash message after signing up' do
+ subject
+
+ expect(response).to redirect_to(new_user_session_path(anchor: 'login-pane'))
+ expect(flash[:notice])
+ .to eq('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.')
+ end
+
+ context 'email confirmation' do
+ context 'when `send_user_confirmation_email` is true' do
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ end
+
+ it 'does not send a confirmation email' do
+ expect { subject }
+ .not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ end
+ end
+ end
+ end
+
+ context 'when the `require_admin_approval_after_user_signup` setting is turned off' do
+ before do
+ stub_application_setting(require_admin_approval_after_user_signup: false)
+ end
+
+ it 'signs up the user in `active` state' do
+ subject
+ created_user = User.find_by(email: 'new@user.com')
+
+ expect(created_user).to be_present
+ expect(created_user.active?).to eq(true)
+ end
+
+ it 'does not show any flash message after signing up' do
+ subject
+
+ expect(flash[:notice]).to be_nil
+ end
+
+ context 'email confirmation' do
+ context 'when `send_user_confirmation_email` is true' do
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ end
+
+ it 'sends a confirmation email' do
+ expect { subject }
+ .to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ end
+ end
+ end
end
end
- context 'when user is part of the experiment' do
+ context 'when the feature is disabled' do
before do
- stub_experiment_for_user(signup_flow: true, terms_opt_in: true)
+ stub_feature_flags(admin_approval_for_new_user_signups: false)
end
- it 'tracks event with right parameters' do
- expect(Gitlab::Tracking).to receive(:event).with(
- 'Growth::Acquisition::Experiment::TermsOptIn',
- 'start',
- label: anything,
- property: 'experimental_group'
- )
+ context 'when the `require_admin_approval_after_user_signup` setting is turned on' do
+ before do
+ stub_application_setting(require_admin_approval_after_user_signup: true)
+ end
- subject
+ it 'signs up the user in `active` state' do
+ subject
+
+ created_user = User.find_by(email: 'new@user.com')
+ expect(created_user).to be_present
+ expect(created_user.active?).to eq(true)
+ end
end
end
end
- end
-
- describe '#create' do
- let(:base_user_params) { { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } }
- let(:user_params) { { user: base_user_params } }
context 'email confirmation' do
- around do |example|
- perform_enqueued_jobs do
- example.run
- end
- end
-
context 'when send_user_confirmation_email is false' do
it 'signs the user in' do
stub_application_setting(send_user_confirmation_email: false)
- expect { post(:create, params: user_params) }.not_to change { ActionMailer::Base.deliveries.size }
- expect(subject.current_user).not_to be_nil
+ expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ expect(controller.current_user).not_to be_nil
end
end
@@ -111,10 +169,8 @@ RSpec.describe RegistrationsController do
end
it 'does not authenticate the user and sends a confirmation email' do
- post(:create, params: user_params)
-
- expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
- expect(subject.current_user).to be_nil
+ expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ expect(controller.current_user).to be_nil
end
end
@@ -125,9 +181,8 @@ RSpec.describe RegistrationsController do
end
it 'authenticates the user and sends a confirmation email' do
- post(:create, params: user_params)
-
- expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
+ expect(controller.current_user).to be_present
expect(response).to redirect_to(users_sign_up_welcome_path)
end
end
@@ -137,7 +192,7 @@ RSpec.describe RegistrationsController do
it 'redirects to sign_in' do
stub_application_setting(signup_enabled: false)
- expect { post(:create, params: user_params) }.not_to change(User, :count)
+ expect { subject }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
end
end
@@ -158,14 +213,14 @@ RSpec.describe RegistrationsController do
it 'displays an error when the reCAPTCHA is not solved' do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
- post(:create, params: user_params)
+ subject
expect(response).to render_template(:new)
expect(flash[:alert]).to eq(_('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'))
end
it 'redirects to the welcome page when the reCAPTCHA is solved' do
- post(:create, params: user_params)
+ subject
expect(response).to redirect_to(users_sign_up_welcome_path)
end
@@ -258,102 +313,26 @@ RSpec.describe RegistrationsController do
end
end
- context 'when terms are enforced' do
- before do
- enforce_terms
- end
-
- it 'redirects back with a notice when the checkbox was not checked' do
- post :create, params: user_params
-
- expect(flash[:alert]).to eq(_('You must accept our Terms of Service and privacy policy in order to register an account'))
- end
-
- it 'creates the user with agreement when terms are accepted' do
- post :create, params: user_params.merge(terms_opt_in: '1')
-
- expect(subject.current_user).to be_present
- expect(subject.current_user.terms_accepted?).to be(true)
- end
-
- context 'when experiment terms_opt_in is enabled' do
+ context 'terms of service' do
+ context 'when terms are enforced' do
before do
- stub_experiment(terms_opt_in: true)
- end
-
- context 'when user is part of the experiment' do
- before do
- stub_experiment_for_user(terms_opt_in: true)
- end
-
- it 'creates the user with accepted terms' do
- post :create, params: user_params
-
- expect(subject.current_user).to be_present
- expect(subject.current_user.terms_accepted?).to be(true)
- end
+ enforce_terms
end
- context 'when user is not part of the experiment' do
- before do
- stub_experiment_for_user(terms_opt_in: false)
- end
-
- it 'creates the user without accepted terms' do
- post :create, params: user_params
+ it 'creates the user with accepted terms' do
+ subject
- expect(flash[:alert]).to eq(_('You must accept our Terms of Service and privacy policy in order to register an account'))
- end
+ expect(controller.current_user).to be_present
+ expect(controller.current_user.terms_accepted?).to be(true)
end
end
- end
-
- describe 'tracking data' do
- context 'with sign up flow and terms_opt_in experiment being enabled' do
- subject { post :create, params: user_params }
-
- before do
- stub_experiment(signup_flow: true, terms_opt_in: true)
- end
-
- it 'records user for the terms_opt_in experiment' do
- expect(controller).to receive(:record_experiment_user).with(:terms_opt_in)
+ context 'when terms are not enforced' do
+ it 'creates the user without accepted terms' do
subject
- end
- context 'when user is not part of the experiment' do
- before do
- stub_experiment_for_user(signup_flow: true, terms_opt_in: false)
- end
-
- it 'tracks event with right parameters' do
- expect(Gitlab::Tracking).to receive(:event).with(
- 'Growth::Acquisition::Experiment::TermsOptIn',
- 'end',
- label: anything,
- property: 'control_group'
- )
-
- subject
- end
- end
-
- context 'when user is part of the experiment' do
- before do
- stub_experiment_for_user(signup_flow: true, terms_opt_in: true)
- end
-
- it 'tracks event with right parameters' do
- expect(Gitlab::Tracking).to receive(:event).with(
- 'Growth::Acquisition::Experiment::TermsOptIn',
- 'end',
- label: anything,
- property: 'experimental_group'
- )
-
- subject
- end
+ expect(controller.current_user).to be_present
+ expect(controller.current_user.terms_accepted?).to be(false)
end
end
end
@@ -361,30 +340,21 @@ RSpec.describe RegistrationsController do
it "logs a 'User Created' message" do
expect(Gitlab::AppLogger).to receive(:info).with(/\AUser Created: username=new_username email=new@user.com.+\z/).and_call_original
- post(:create, params: user_params)
+ subject
end
it 'handles when params are new_user' do
post(:create, params: { new_user: base_user_params })
- expect(subject.current_user).not_to be_nil
+ expect(controller.current_user).not_to be_nil
end
- context 'with the experimental signup flow enabled and the user is part of the experimental group' do
- before do
- stub_experiment(signup_flow: true)
- stub_experiment_for_user(signup_flow: true)
- end
-
- let(:base_user_params) { { first_name: 'First', last_name: 'Last', username: 'new_username', email: 'new@user.com', password: 'Any_password' } }
-
- it 'sets name from first and last name' do
- post :create, params: { new_user: base_user_params }
+ it 'sets name from first and last name' do
+ post :create, params: { new_user: base_user_params }
- expect(User.last.first_name).to eq(base_user_params[:first_name])
- expect(User.last.last_name).to eq(base_user_params[:last_name])
- expect(User.last.name).to eq("#{base_user_params[:first_name]} #{base_user_params[:last_name]}")
- end
+ expect(User.last.first_name).to eq(base_user_params[:first_name])
+ expect(User.last.last_name).to eq(base_user_params[:last_name])
+ expect(User.last.name).to eq("#{base_user_params[:first_name]} #{base_user_params[:last_name]}")
end
end
@@ -507,10 +477,16 @@ RSpec.describe RegistrationsController do
patch :update_registration, params: { user: { role: 'software_developer', setup_for_company: 'false' } }
end
- before do
- sign_in(create(:user))
+ context 'without a signed in user' do
+ it { is_expected.to redirect_to new_user_registration_path }
end
- it { is_expected.to redirect_to(dashboard_projects_path)}
+ context 'with a signed in user' do
+ before do
+ sign_in(create(:user))
+ end
+
+ it { is_expected.to redirect_to(dashboard_projects_path)}
+ end
end
end
diff --git a/spec/controllers/runner_setup_controller_spec.rb b/spec/controllers/runner_setup_controller_spec.rb
new file mode 100644
index 00000000000..0b237500907
--- /dev/null
+++ b/spec/controllers/runner_setup_controller_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RunnerSetupController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET #platforms' do
+ it 'renders the platforms' do
+ get :platforms
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key("windows")
+ expect(json_response).to have_key("kubernetes")
+ end
+ end
+end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index f244392bbad..a0cb696828d 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -183,7 +183,7 @@ RSpec.describe SearchController do
end
it_behaves_like 'tracking unique hll events', :search_track_unique_users do
- subject { get :show, params: { scope: 'projects', search: 'term' }, format: format }
+ subject(:request) { get :show, params: { scope: 'projects', search: 'term' } }
let(:target_id) { 'i_search_total' }
let(:expected_type) { instance_of(String) }
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 688539f2a03..75bcc32e6f3 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -78,6 +78,9 @@ RSpec.describe SessionsController do
end
context 'when using standard authentications' do
+ let(:user) { create(:user) }
+ let(:post_action) { post(:create, params: { user: { login: user.username, password: user.password } }) }
+
context 'invalid password' do
it 'does not authenticate user' do
post(:create, params: { user: { login: 'invalid', password: 'invalid' } })
@@ -87,6 +90,36 @@ RSpec.describe SessionsController do
end
end
+ context 'a blocked user' do
+ it 'does not authenticate the user' do
+ user.block!
+ post_action
+
+ expect(@request.env['warden']).not_to be_authenticated
+ expect(flash[:alert]).to include('Your account has been blocked')
+ end
+ end
+
+ context 'a `blocked pending approval` user' do
+ it 'does not authenticate the user' do
+ user.block_pending_approval!
+ post_action
+
+ expect(@request.env['warden']).not_to be_authenticated
+ expect(flash[:alert]).to include('Your account is pending approval from your GitLab administrator and hence blocked')
+ end
+ end
+
+ context 'an internal user' do
+ it 'does not authenticate the user' do
+ user.ghost!
+ post_action
+
+ expect(@request.env['warden']).not_to be_authenticated
+ expect(flash[:alert]).to include('Your account does not have the required permission to login')
+ end
+ end
+
context 'when using valid password', :clean_gitlab_redis_shared_state do
let(:user) { create(:user) }
let(:user_params) { { login: user.username, password: user.password } }
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 6517922d92a..1ccba7f9114 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe SnippetsController do
let_it_be(:user) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:public_snippet) { create(:personal_snippet, :public, :repository, author: user) }
describe 'GET #index' do
let(:base_params) { { username: user.username } }
@@ -12,10 +14,6 @@ RSpec.describe SnippetsController do
it_behaves_like 'paginated collection' do
let(:collection) { Snippet.all }
let(:params) { { username: user.username } }
-
- before do
- create(:personal_snippet, :public, author: user)
- end
end
it 'renders snippets of a user when username is present' do
@@ -86,12 +84,6 @@ RSpec.describe SnippetsController do
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(:ok)
end
-
- it 'renders the blob from the repository' do
- subject
-
- expect(assigns(:blob)).to eq(personal_snippet.blobs.first)
- end
end
context 'when the personal snippet is private' do
@@ -103,8 +95,7 @@ RSpec.describe SnippetsController do
end
context 'when signed in user is not the author' do
- let(:other_author) { create(:author) }
- let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_user) }
it 'responds with status 404' do
get :show, params: { id: other_personal_snippet.to_param }
@@ -164,7 +155,7 @@ RSpec.describe SnippetsController do
end
context 'when the personal snippet is public' do
- let_it_be(:personal_snippet) { create(:personal_snippet, :public, :repository, author: user) }
+ let(:personal_snippet) { public_snippet }
context 'when signed in' do
before do
@@ -172,22 +163,22 @@ RSpec.describe SnippetsController do
end
it_behaves_like 'successful response' do
- subject { get :show, params: { id: personal_snippet.to_param } }
+ subject { get :show, params: { id: public_snippet.to_param } }
end
it 'responds with status 200 when embeddable content is requested' do
- get :show, params: { id: personal_snippet.to_param }, format: :js
+ get :show, params: { id: public_snippet.to_param }, format: :js
- expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(assigns(:snippet)).to eq(public_snippet)
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when not signed in' do
it 'renders the snippet' do
- get :show, params: { id: personal_snippet.to_param }
+ get :show, params: { id: public_snippet.to_param }
- expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(assigns(:snippet)).to eq(public_snippet)
expect(response).to have_gitlab_http_status(:ok)
end
end
@@ -200,7 +191,7 @@ RSpec.describe SnippetsController do
end
it 'responds with status 404' do
- get :show, params: { id: 'doesntexist' }
+ get :show, params: { id: non_existing_record_id }
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -208,260 +199,43 @@ RSpec.describe SnippetsController do
context 'when not signed in' do
it 'responds with status 404' do
- get :show, params: { id: 'doesntexist' }
+ get :show, params: { id: non_existing_record_id }
expect(response).to redirect_to(new_user_session_path)
end
end
end
- end
-
- describe 'POST #create' do
- def create_snippet(snippet_params = {}, additional_params = {})
- sign_in(user)
-
- post :create, params: {
- personal_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
- }.merge(additional_params)
-
- Snippet.last
- end
-
- it 'creates the snippet correctly' do
- snippet = create_snippet(visibility_level: Snippet::PRIVATE)
-
- expect(snippet.title).to eq('Title')
- expect(snippet.content).to eq('Content')
- expect(snippet.description).to eq('Description')
- end
-
- context 'when user is not allowed to create a personal snippet' do
- let(:user) { create(:user, :external) }
-
- it 'responds with status 404' do
- aggregate_failures do
- expect do
- create_snippet(visibility_level: Snippet::PUBLIC)
- end.not_to change { Snippet.count }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'when the controller receives the files param' do
- let(:files) { %w(foo bar) }
-
- it 'passes the files param to the snippet create service' do
- expect(Snippets::CreateService).to receive(:new).with(nil, user, hash_including(files: files)).and_call_original
-
- create_snippet({ title: nil }, { files: files })
- end
- end
-
- context 'when the snippet is spam' do
- before do
- allow_next_instance_of(Spam::AkismetService) do |instance|
- allow(instance).to receive(:spam?).and_return(true)
- end
- end
-
- context 'when the snippet is private' do
- it 'creates the snippet' do
- expect { create_snippet(visibility_level: Snippet::PRIVATE) }
- .to change { Snippet.count }.by(1)
- end
- end
-
- context 'when the snippet is public' do
- it 'rejects the snippet' do
- expect { create_snippet(visibility_level: Snippet::PUBLIC) }
- .not_to change { Snippet.count }
- end
-
- it 'creates a spam log' do
- expect { create_snippet(visibility_level: Snippet::PUBLIC) }
- .to log_spam(title: 'Title', user: user, noteable_type: 'PersonalSnippet')
- end
-
- it 'renders :new with reCAPTCHA disabled' do
- stub_application_setting(recaptcha_enabled: false)
-
- create_snippet(visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:new)
- end
-
- context 'reCAPTCHA enabled' do
- before do
- stub_application_setting(recaptcha_enabled: true)
- end
-
- it 'renders :verify' do
- create_snippet(visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:verify)
- end
-
- it 'renders snippet page' do
- spammy_title = 'Whatever'
-
- spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
- snippet = create_snippet({ title: spammy_title },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
-
- expect(response).to redirect_to(snippet_path(snippet))
- end
- end
- end
- end
- end
-
- describe 'PUT #update' do
- let(:project) { create :project }
- let(:visibility_level) { Snippet::PUBLIC }
- let(:snippet) { create :personal_snippet, author: user, project: project, visibility_level: visibility_level }
-
- def update_snippet(snippet_params = {}, additional_params = {})
- sign_in(user)
-
- put :update, params: {
- id: snippet.id,
- personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
- }.merge(additional_params)
-
- snippet.reload
- end
-
- context 'when the snippet is spam' do
- before do
- allow_next_instance_of(Spam::AkismetService) do |instance|
- allow(instance).to receive(:spam?).and_return(true)
- end
- end
-
- context 'when the snippet is private' do
- let(:visibility_level) { Snippet::PRIVATE }
-
- it 'updates the snippet' do
- expect { update_snippet(title: 'Foo') }
- .to change { snippet.reload.title }.to('Foo')
- end
- end
-
- context 'when a private snippet is made public' do
- let(:visibility_level) { Snippet::PRIVATE }
-
- it 'rejects the snippet' do
- expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
- .not_to change { snippet.reload.title }
- end
-
- it 'creates a spam log' do
- expect { update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC) }
- .to log_spam(title: 'Foo', user: user, noteable_type: 'PersonalSnippet')
- end
-
- it 'renders :edit with reCAPTCHA disabled' do
- stub_application_setting(recaptcha_enabled: false)
-
- update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:edit)
- end
-
- context 'reCAPTCHA enabled' do
- before do
- stub_application_setting(recaptcha_enabled: true)
- end
-
- it 'renders :verify' do
- update_snippet(title: 'Foo', visibility_level: Snippet::PUBLIC)
-
- expect(response).to render_template(:verify)
- end
-
- it 'renders snippet page when reCAPTCHA verified' do
- spammy_title = 'Whatever'
-
- spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
- snippet = update_snippet({ title: spammy_title, visibility_level: Snippet::PUBLIC },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
-
- expect(response).to redirect_to(snippet_path(snippet))
- end
- end
- end
-
- context 'when the snippet is public' do
- it 'rejects the snippet' do
- expect { update_snippet(title: 'Foo') }
- .not_to change { snippet.reload.title }
- end
-
- it 'creates a spam log' do
- expect {update_snippet(title: 'Foo') }
- .to log_spam(title: 'Foo', user: user, noteable_type: 'PersonalSnippet')
- end
-
- it 'renders :edit with reCAPTCHA disabled' do
- stub_application_setting(recaptcha_enabled: false)
-
- update_snippet(title: 'Foo')
-
- expect(response).to render_template(:edit)
- end
-
- context 'recaptcha enabled' do
- before do
- stub_application_setting(recaptcha_enabled: true)
- end
-
- it 'renders :verify' do
- update_snippet(title: 'Foo')
-
- expect(response).to render_template(:verify)
- end
-
- it 'renders snippet page when reCAPTCHA verified' do
- spammy_title = 'Whatever'
- spam_logs = create_list(:spam_log, 2, user: user, title: spammy_title)
- snippet = update_snippet({ title: spammy_title },
- { spam_log_id: spam_logs.last.id,
- recaptcha_verification: true })
+ context 'when requesting JSON' do
+ it 'renders the blob from the repository' do
+ get :show, params: { id: public_snippet.to_param }, format: :json
- expect(response).to redirect_to(snippet_path(snippet))
- end
- end
+ expect(assigns(:blob)).to eq(public_snippet.blobs.first)
end
end
end
describe 'POST #mark_as_spam' do
- let(:snippet) { create(:personal_snippet, :public, author: user) }
-
before do
allow_next_instance_of(Spam::AkismetService) do |instance|
allow(instance).to receive_messages(submit_spam: true)
end
+
stub_application_setting(akismet_enabled: true)
end
def mark_as_spam
admin = create(:admin)
- create(:user_agent_detail, subject: snippet)
+ create(:user_agent_detail, subject: public_snippet)
sign_in(admin)
- post :mark_as_spam, params: { id: snippet.id }
+ post :mark_as_spam, params: { id: public_snippet.id }
end
it 'updates the snippet' do
mark_as_spam
- expect(snippet.reload).not_to be_submittable_as_spam
+ expect(public_snippet.reload).not_to be_submittable_as_spam
end
end
@@ -489,9 +263,7 @@ RSpec.describe SnippetsController do
shared_examples 'CRLF line ending' do
let(:content) { "first line\r\nsecond line\r\nthird line" }
let(:formatted_content) { content.gsub(/\r\n/, "\n") }
- let(:snippet) do
- create(:personal_snippet, :public, :repository, author: user, content: content)
- end
+ let(:snippet) { public_snippet }
before do
allow_next_instance_of(Blob) do |instance|
@@ -560,8 +332,7 @@ RSpec.describe SnippetsController do
end
context 'when signed in user is not the author' do
- let(:other_author) { create(:author) }
- let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_user) }
it 'responds with status 404' do
get :raw, params: { id: other_personal_snippet.to_param }
@@ -605,7 +376,7 @@ RSpec.describe SnippetsController do
end
context 'when the personal snippet is public' do
- let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) }
+ let(:snippet) { public_snippet }
context 'when signed in' do
before do
@@ -632,7 +403,7 @@ RSpec.describe SnippetsController do
end
it 'responds with status 404' do
- get :raw, params: { id: 'doesntexist' }
+ get :raw, params: { id: non_existing_record_id }
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -640,7 +411,7 @@ RSpec.describe SnippetsController do
context 'when not signed in' do
it 'redirects to the sign in path' do
- get :raw, params: { id: 'doesntexist' }
+ get :raw, params: { id: non_existing_record_id }
expect(response).to redirect_to(new_user_session_path)
end
@@ -649,11 +420,10 @@ RSpec.describe SnippetsController do
end
context 'award emoji on snippets' do
- let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
- let(:another_user) { create(:user) }
+ let(:personal_snippet) { public_snippet }
before do
- sign_in(another_user)
+ sign_in(other_user)
end
describe 'POST #toggle_award_emoji' do
@@ -678,66 +448,12 @@ RSpec.describe SnippetsController do
end
describe 'POST #preview_markdown' do
- let(:snippet) { create(:personal_snippet, :public) }
-
it 'renders json in a correct format' do
sign_in(user)
- post :preview_markdown, params: { id: snippet, text: '*Markdown* text' }
+ post :preview_markdown, params: { id: public_snippet, text: '*Markdown* text' }
expect(json_response.keys).to match_array(%w(body references))
end
end
-
- describe 'DELETE #destroy' do
- let!(:snippet) { create :personal_snippet, author: user }
-
- context 'when current user has ability to destroy the snippet' do
- before do
- sign_in(user)
- end
-
- it 'removes the snippet' do
- delete :destroy, params: { id: snippet.to_param }
-
- expect { snippet.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
-
- context 'when snippet is succesfuly destroyed' do
- it 'redirects to the project snippets page' do
- delete :destroy, params: { id: snippet.to_param }
-
- expect(response).to redirect_to(dashboard_snippets_path)
- end
- end
-
- context 'when snippet is not destroyed' do
- before do
- allow(snippet).to receive(:destroy).and_return(false)
- controller.instance_variable_set(:@snippet, snippet)
- end
-
- it 'renders the snippet page with errors' do
- delete :destroy, params: { id: snippet.to_param }
-
- expect(flash[:alert]).to eq('Failed to remove snippet.')
- expect(response).to redirect_to(snippet_path(snippet))
- end
- end
- end
-
- context 'when current_user does not have ability to destroy the snippet' do
- let(:another_user) { create(:user) }
-
- before do
- sign_in(another_user)
- end
-
- it 'responds with status 404' do
- delete :destroy, params: { id: snippet.to_param }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
end