diff options
Diffstat (limited to 'spec/controllers')
26 files changed, 798 insertions, 1555 deletions
diff --git a/spec/controllers/admin/dev_ops_report_controller_spec.rb b/spec/controllers/admin/dev_ops_report_controller_spec.rb index 0be30fff0c2..913921b9630 100644 --- a/spec/controllers/admin/dev_ops_report_controller_spec.rb +++ b/spec/controllers/admin/dev_ops_report_controller_spec.rb @@ -3,6 +3,12 @@ require 'spec_helper' RSpec.describe Admin::DevOpsReportController do + describe 'show_adoption?' do + it 'is always false' do + expect(controller.show_adoption?).to be false + end + end + describe 'GET #show' do context 'as admin' do let(:user) { create(:admin) } diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index b5f411c9121..d81b067ffb6 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -77,4 +77,34 @@ RSpec.describe Admin::ProjectsController do expect(response.body).to match(project.name) end end + + describe 'PUT /projects/transfer/:id' do + let_it_be(:project, reload: true) { create(:project) } + let_it_be(:new_namespace) { create(:namespace) } + + it 'updates namespace' do + put :transfer, params: { namespace_id: project.namespace.path, new_namespace_id: new_namespace.id, id: project.path } + + project.reload + + expect(project.namespace).to eq(new_namespace) + expect(response).to have_gitlab_http_status(:redirect) + expect(response).to redirect_to(admin_project_path(project)) + end + + context 'when project transfer fails' do + it 'flashes error' do + old_namespace = project.namespace + + put :transfer, params: { namespace_id: old_namespace.path, new_namespace_id: nil, id: project.path } + + project.reload + + expect(project.namespace).to eq(old_namespace) + expect(response).to have_gitlab_http_status(:redirect) + expect(response).to redirect_to(admin_project_path(project)) + expect(flash[:alert]).to eq s_('TransferProject|Please select a new namespace for your project.') + end + end + end end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 9342513d224..4a729008e67 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -720,6 +720,49 @@ RSpec.describe ApplicationController do end end + describe '#stream_csv_headers' do + controller(described_class) do + def index + respond_to do |format| + format.csv do + stream_csv_headers('test.csv') + + self.response_body = fixture_file_upload('spec/fixtures/csv_comma.csv') + end + end + end + end + + subject { get :index, format: :csv } + + before do + sign_in(user) + end + + it 'sets no-cache headers', :aggregate_failures do + subject + + expect(response.headers['Cache-Control']).to eq 'no-cache, no-store' + expect(response.headers['Pragma']).to eq 'no-cache' + expect(response.headers['Expires']).to eq 'Fri, 01 Jan 1990 00:00:00 GMT' + end + + it 'sets stream headers', :aggregate_failures do + subject + + expect(response.headers['Content-Length']).to be nil + expect(response.headers['X-Accel-Buffering']).to eq 'no' + expect(response.headers['Last-Modified']).to eq '0' + end + + it 'sets the csv specific headers', :aggregate_failures do + subject + + expect(response.headers['Content-Type']).to eq 'text/csv; charset=utf-8; header=present' + expect(response.headers['Content-Disposition']).to eq "attachment; filename=\"test.csv\"" + end + end + context 'Gitlab::Session' do controller(described_class) do prepend_before_action do diff --git a/spec/controllers/concerns/redis_tracking_spec.rb b/spec/controllers/concerns/redis_tracking_spec.rb index 831f5ad7bb1..ef59adf8c1d 100644 --- a/spec/controllers/concerns/redis_tracking_spec.rb +++ b/spec/controllers/concerns/redis_tracking_spec.rb @@ -42,7 +42,7 @@ RSpec.describe RedisTracking do def expect_tracking expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event) - .with(instance_of(String), 'g_compliance_approval_rules') + .with('g_compliance_approval_rules', values: instance_of(String)) end def expect_no_tracking diff --git a/spec/controllers/concerns/spammable_actions_spec.rb b/spec/controllers/concerns/spammable_actions_spec.rb new file mode 100644 index 00000000000..3b5b4d11a9b --- /dev/null +++ b/spec/controllers/concerns/spammable_actions_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SpammableActions do + controller(ActionController::Base) do + include SpammableActions + + # #create is used to test spammable_params + # for testing purposes + def create + spam_params = spammable_params + + # replace the actual request with a string in the JSON response, all we care is that it got set + spam_params[:request] = 'this is the request' if spam_params[:request] + + # just return the params in the response so they can be verified in this fake controller spec. + # Normally, they are processed further by the controller action + render json: spam_params.to_json, status: :ok + end + + # #update is used to test recaptcha_check_with_fallback + # for testing purposes + def update + should_redirect = params[:should_redirect] == 'true' + + recaptcha_check_with_fallback(should_redirect) { render json: :ok } + end + + private + + def spammable_path + '/fake_spammable_path' + end + end + + before do + # Ordinarily we would not stub a method on the class under test, but :ensure_spam_config_loaded! + # returns false in the test environment, and is also strong_memoized, so we need to stub it + allow(controller).to receive(:ensure_spam_config_loaded!) { true } + end + + describe '#spammable_params' do + subject { post :create, format: :json, params: params } + + shared_examples 'expects request param only' do + it do + subject + + expect(response).to be_successful + expect(json_response).to eq({ 'request' => 'this is the request' }) + end + end + + shared_examples 'expects all spammable params' do + it do + subject + + expect(response).to be_successful + expect(json_response['request']).to eq('this is the request') + expect(json_response['recaptcha_verified']).to eq(true) + expect(json_response['spam_log_id']).to eq('1') + end + end + + let(:recaptcha_response) { nil } + let(:spam_log_id) { nil } + + context 'when recaptcha response is not present' do + let(:params) do + { + spam_log_id: spam_log_id + } + end + + it_behaves_like 'expects request param only' + end + + context 'when recaptcha response is present' do + let(:recaptcha_response) { 'abd123' } + let(:params) do + { + 'g-recaptcha-response': recaptcha_response, + spam_log_id: spam_log_id + } + end + + context 'when verify_recaptcha returns falsey' do + before do + expect(controller).to receive(:verify_recaptcha).with( + { + response: recaptcha_response + }) { false } + end + + it_behaves_like 'expects request param only' + end + + context 'when verify_recaptcha returns truthy' do + let(:spam_log_id) { 1 } + + before do + expect(controller).to receive(:verify_recaptcha).with( + { + response: recaptcha_response + }) { true } + end + + it_behaves_like 'expects all spammable params' + end + end + end + + describe '#recaptcha_check_with_fallback' do + shared_examples 'yields to block' do + it do + subject + + expect(json_response).to eq({ json: 'ok' }) + end + end + + let(:format) { :html } + + subject { post :update, format: format, params: params } + + let(:spammable) { double(:spammable) } + let(:should_redirect) { nil } + let(:params) do + { + should_redirect: should_redirect + } + end + + before do + routes.draw { get 'update' => 'anonymous#update' } + allow(controller).to receive(:spammable) { spammable } + end + + context 'when should_redirect is true and spammable is valid' do + let(:should_redirect) { true } + + before do + allow(spammable).to receive(:valid?) { true } + end + + it 'redirects to spammable_path' do + expect(subject).to redirect_to('/fake_spammable_path') + end + end + + context 'when should_redirect is false or spammable is not valid' do + before do + allow(spammable).to receive(:valid?) { false } + end + + # NOTE: Not adding coverage of details of render_recaptcha?, the plan is to refactor it out + # of this module anyway as part of adding support for the GraphQL reCAPTCHA flow. + + context 'when render_recaptcha? is true' do + before do + expect(controller).to receive(:render_recaptcha?) { true } + end + + context 'when format is :html' do + it 'renders :verify' do + expect(controller).to receive(:render).with(:verify) + + subject + end + end + + context 'when format is :json' do + let(:format) { :json } + let(:recaptcha_html) { '<recaptcha-html/>' } + + it 'renders json with recaptcha_html' do + expect(controller).to receive(:render_to_string).with( + { + partial: 'shared/recaptcha_form', + formats: :html, + locals: { + spammable: spammable, + script: false, + has_submit: false + } + } + ) { recaptcha_html } + + subject + + expect(json_response).to eq({ 'recaptcha_html' => recaptcha_html }) + end + end + end + end + end +end diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb index 2719b7c8a24..dcec8012f03 100644 --- a/spec/controllers/dashboard/projects_controller_spec.rb +++ b/spec/controllers/dashboard/projects_controller_spec.rb @@ -13,8 +13,8 @@ RSpec.describe Dashboard::ProjectsController, :aggregate_failures do end context 'user logged in' do - let_it_be(:project) { create(:project) } - let_it_be(:project2) { create(:project) } + let_it_be(:project) { create(:project, name: 'Project 1') } + let_it_be(:project2) { create(:project, name: 'Project Two') } let(:projects) { [project, project2] } before_all do @@ -36,10 +36,7 @@ RSpec.describe Dashboard::ProjectsController, :aggregate_failures do end end - it 'orders the projects by last activity by default' do - project.update!(last_repository_updated_at: 3.days.ago, last_activity_at: 3.days.ago) - project2.update!(last_repository_updated_at: 10.days.ago, last_activity_at: 10.days.ago) - + it 'orders the projects by name by default' do get :index expect(assigns(:projects)).to eq(projects) diff --git a/spec/controllers/dashboard/snippets_controller_spec.rb b/spec/controllers/dashboard/snippets_controller_spec.rb index d981f738e70..016a9f53129 100644 --- a/spec/controllers/dashboard/snippets_controller_spec.rb +++ b/spec/controllers/dashboard/snippets_controller_spec.rb @@ -28,5 +28,24 @@ RSpec.describe Dashboard::SnippetsController do end it_behaves_like 'snippets sort order' + + context 'when views are rendered' do + render_views + + it 'avoids N+1 database queries' do + # Warming call to load everything non snippet related + get(:index) + + project = create(:project, namespace: user.namespace) + create(:project_snippet, project: project, author: user) + + control_count = ActiveRecord::QueryRecorder.new { get(:index) }.count + + project = create(:project, namespace: user.namespace) + create(:project_snippet, project: project, author: user) + + expect { get(:index) }.not_to exceed_query_limit(control_count) + end + end end end diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb index 4ec890a528f..cfbd129388d 100644 --- a/spec/controllers/explore/projects_controller_spec.rb +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Explore::ProjectsController do end shared_examples "blocks high page numbers" do - let(:page_limit) { 200 } + let(:page_limit) { described_class::PAGE_LIMIT } context "page number is too high" do [:index, :trending, :starred].each do |endpoint| diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 33041f1af9f..b2320615778 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -9,8 +9,6 @@ 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 @@ -34,41 +32,12 @@ RSpec.describe Groups::LabelsController do subgroup.add_owner(user) end - RSpec.shared_examples 'returns ancestor group labels' do - it 'returns ancestor group labels' do - get :index, params: params, format: :json + it 'returns ancestor group labels' do + params = { group_id: subgroup, only_group_labels: true } + 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]) - 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' + label_ids = json_response.map {|label| label['title']} + expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) end end diff --git a/spec/controllers/import/bulk_imports_controller_spec.rb b/spec/controllers/import/bulk_imports_controller_spec.rb index 436daed0af6..d1c138617bb 100644 --- a/spec/controllers/import/bulk_imports_controller_spec.rb +++ b/spec/controllers/import/bulk_imports_controller_spec.rb @@ -63,9 +63,16 @@ RSpec.describe Import::BulkImportsController do ) end + let(:client_params) do + { + top_level_only: true, + min_access_level: Gitlab::Access::MAINTAINER + } + 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) + allow(client).to receive(:get).with('groups', client_params).and_return(client_response) end it 'returns serialized group data' do @@ -73,6 +80,17 @@ RSpec.describe Import::BulkImportsController do expect(json_response).to eq({ importable_data: client_response.parsed_response }.as_json) end + + context 'when filtering' do + it 'returns filtered result' do + filter = 'test' + search_params = client_params.merge(search: filter) + + expect(client).to receive(:get).with('groups', search_params).and_return(client_response) + + get :status, format: :json, params: { filter: filter } + end + end end context 'when host url is local or not http' do @@ -131,6 +149,22 @@ RSpec.describe Import::BulkImportsController do end end + describe 'GET realtime_changes' do + let_it_be(:bulk_import) { create(:bulk_import, :created, user: user) } + + it 'returns bulk imports created by current user' do + get :realtime_changes + + expect(json_response).to eq([{ 'id' => bulk_import.id, 'status_name' => bulk_import.status_name.to_s }]) + end + + it 'sets a Poll-Interval header' do + get :realtime_changes + + expect(response.headers['Poll-Interval']).to eq(Import::BulkImportsController::POLLING_INTERVAL.to_s) + end + end + describe 'POST create' do let(:instance_url) { "http://fake-intance" } let(:pat) { "fake-pat" } diff --git a/spec/controllers/jwks_controller_spec.rb b/spec/controllers/jwks_controller_spec.rb deleted file mode 100644 index 013ec01eba2..00000000000 --- a/spec/controllers/jwks_controller_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe JwksController do - describe 'GET #index' do - let(:ci_jwt_signing_key) { OpenSSL::PKey::RSA.generate(1024) } - let(:ci_jwk) { ci_jwt_signing_key.to_jwk } - let(:oidc_jwk) { OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key).to_jwk } - - before do - stub_application_setting(ci_jwt_signing_key: ci_jwt_signing_key.to_s) - end - - it 'returns signing keys used to sign CI_JOB_JWT' do - get :index - - expect(response).to have_gitlab_http_status(:ok) - - ids = json_response['keys'].map { |jwk| jwk['kid'] } - expect(ids).to contain_exactly(ci_jwk['kid'], oidc_jwk['kid']) - end - - it 'does not leak private key data' do - get :index - - aggregate_failures do - json_response['keys'].each do |jwk| - expect(jwk.keys).to contain_exactly('kty', 'kid', 'e', 'n', 'use', 'alg') - expect(jwk['use']).to eq('sig') - expect(jwk['alg']).to eq('RS256') - end - end - end - end -end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index f350d7378dc..9fa90dde997 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -28,8 +28,38 @@ RSpec.describe MetricsController, :request_store do end end + shared_examples_for 'protected metrics endpoint' do |examples| + context 'accessed from whitelisted ip' do + before do + allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(whitelisted_ip) + end + + it_behaves_like examples + end + + context 'accessed from ip in whitelisted range' do + before do + allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(ip_in_whitelisted_range) + end + + it_behaves_like examples + end + + context 'accessed from not whitelisted ip' do + before do + allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(not_whitelisted_ip) + end + + it 'returns the expected error response' do + get :index + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + describe '#index' do - shared_examples_for 'endpoint providing metrics' do + shared_examples_for 'providing metrics' do it 'returns prometheus metrics' do get :index @@ -51,32 +81,35 @@ RSpec.describe MetricsController, :request_store do end end - context 'accessed from whitelisted ip' do - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(whitelisted_ip) - end - - it_behaves_like 'endpoint providing metrics' - end + include_examples 'protected metrics endpoint', 'providing metrics' + end - context 'accessed from ip in whitelisted range' do - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(ip_in_whitelisted_range) + describe '#system' do + shared_examples_for 'providing system stats' do + let(:summary) do + { + version: 'ruby-3.0-patch1', + memory_rss: 1024 + } end - it_behaves_like 'endpoint providing metrics' - end + it 'renders system stats JSON' do + expect(Prometheus::PidProvider).to receive(:worker_id).and_return('worker-0') + expect(Gitlab::Metrics::System).to receive(:summary).and_return(summary) - context 'accessed from not whitelisted ip' do - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(not_whitelisted_ip) - end - - it 'returns the expected error response' do - get :index + get :system - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:ok) + expect(response_json['version']).to eq('ruby-3.0-patch1') + expect(response_json['worker_id']).to eq('worker-0') + expect(response_json['memory_rss']).to eq(1024) end end + + include_examples 'protected metrics endpoint', 'providing system stats' + end + + def response_json + Gitlab::Json.parse(response.body) end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index a56425c2a22..16be7394174 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -7,6 +7,82 @@ RSpec.describe Projects::BlobController do let(:project) { create(:project, :public, :repository) } + describe "GET new" do + context 'with no jobs' do + let_it_be(:user) { create(:user) } + let_it_be(:file_name) { '.gitlab-ci.yml' } + + def request + get(:new, params: { namespace_id: project.namespace, project_id: project, id: 'master', file_name: file_name } ) + end + + before do + project.add_maintainer(user) + sign_in(user) + + stub_experiment(ci_syntax_templates: experiment_active) + stub_experiment_for_subject(ci_syntax_templates: in_experiment_group) + end + + context 'when the experiment is not active' do + let(:experiment_active) { false } + let(:in_experiment_group) { false } + + it 'does not record the experiment user' do + expect(Experiment).not_to receive(:add_user) + + request + end + end + + context 'when the experiment is active and the user is in the control group' do + let(:experiment_active) { true } + let(:in_experiment_group) { false } + + it 'records the experiment user in the control group' do + expect(Experiment).to receive(:add_user) + .with(:ci_syntax_templates, :control, user, namespace_id: project.namespace_id) + + request + end + end + + context 'when the experiment is active and the user is in the experimental group' do + let(:experiment_active) { true } + let(:in_experiment_group) { true } + + it 'records the experiment user in the experimental group' do + expect(Experiment).to receive(:add_user) + .with(:ci_syntax_templates, :experimental, user, namespace_id: project.namespace_id) + + request + end + + context 'when requesting a non default config file type' do + let(:file_name) { '.non_default_ci_config' } + let(:project) { create(:project, :public, :repository, ci_config_path: file_name) } + + it 'records the experiment user in the experimental group' do + expect(Experiment).to receive(:add_user) + .with(:ci_syntax_templates, :experimental, user, namespace_id: project.namespace_id) + + request + end + end + + context 'when requesting a different file type' do + let(:file_name) { '.gitignore' } + + it 'does not record the experiment user' do + expect(Experiment).not_to receive(:add_user) + + request + end + end + end + end + end + describe "GET show" do def request get(:show, params: { namespace_id: project.namespace, project_id: project, id: id }) diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index d1142cbd129..83ad36b217f 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Projects::EnvironmentsController do include MetricsDashboardHelpers + include KubernetesHelpers let_it_be(:project) { create(:project) } let_it_be(:maintainer) { create(:user, name: 'main-dos').tap { |u| project.add_maintainer(u) } } @@ -34,6 +35,9 @@ RSpec.describe Projects::EnvironmentsController do context 'when requesting JSON response for folders' do before do + allow_any_instance_of(Environment).to receive(:has_terminals?).and_return(true) + allow_any_instance_of(Environment).to receive(:rollout_status).and_return(kube_deployment_rollout_status) + create(:environment, project: project, name: 'staging/review-1', state: :available) @@ -91,9 +95,11 @@ RSpec.describe Projects::EnvironmentsController do it 'responds with a payload describing available environments' do expect(environments.count).to eq 2 expect(environments.first['name']).to eq 'production' + expect(environments.first['latest']['rollout_status']).to be_present expect(environments.second['name']).to eq 'staging' expect(environments.second['size']).to eq 2 expect(environments.second['latest']['name']).to eq 'staging/review-2' + expect(environments.second['latest']['rollout_status']).to be_present end it 'contains values describing environment scopes sizes' do diff --git a/spec/controllers/projects/feature_flags_controller_spec.rb b/spec/controllers/projects/feature_flags_controller_spec.rb index d5fc80bd5a7..f69cc0ddfd8 100644 --- a/spec/controllers/projects/feature_flags_controller_spec.rb +++ b/spec/controllers/projects/feature_flags_controller_spec.rb @@ -845,413 +845,64 @@ RSpec.describe Projects::FeatureFlagsController do 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 'with a legacy feature flag' do + subject { put(:update, params: params, format: :json) } - 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) + let!(:feature_flag) do + create(:operations_feature_flag, + :legacy_flag, + name: 'ci_live_trace', + active: true, + project: project) 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 - } - ] + name: 'ci_new_live_trace' } } 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: {} }] - }]) + context 'when user is reporter' do + let(:user) { reporter } - expect(response).to have_gitlab_http_status(:ok) - scope_json = json_response['scopes'].find do |s| - s['environment_scope'] == 'production' + it 'returns 404' do + is_expected.to have_gitlab_http_status(:not_found) 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" + context "when changing 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 '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' + it 'returns 400' do + is_expected.to have_gitlab_http_status(:bad_request) 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) - + it 'does not update a legacy feature flag' do 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 @@ -1517,15 +1168,6 @@ RSpec.describe Projects::FeatureFlagsController do expect(response).to have_gitlab_http_status(:ok) expect(json_response['strategies'].first['scopes']).to eq([]) 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 diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 12c8c84dd77..d3bdf1baaae 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -227,6 +227,22 @@ RSpec.describe Projects::IssuesController do end end + describe "GET #show" do + before do + sign_in(user) + project.add_developer(user) + end + + it "returns issue_email_participants" do + participants = create_list(:issue_email_participant, 2, issue: issue) + + get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }, format: :json + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['issue_email_participants']).to contain_exactly({ "email" => participants[0].email }, { "email" => participants[1].email }) + end + end + describe 'GET #new' do it 'redirects to signin if not logged in' do get :new, params: { namespace_id: project.namespace, project_id: project } @@ -1003,7 +1019,7 @@ RSpec.describe Projects::IssuesController do def update_verified_issue update_issue( issue_params: { title: spammy_title }, - additional_params: { spam_log_id: spam_logs.last.id, recaptcha_verification: true }) + additional_params: { spam_log_id: spam_logs.last.id, 'g-recaptcha-response': true }) end it 'returns 200 status' do @@ -1021,7 +1037,7 @@ RSpec.describe Projects::IssuesController do it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do spam_log = create(:spam_log) - expect { update_issue(issue_params: { spam_log_id: spam_log.id, recaptcha_verification: true }) } + expect { update_issue(issue_params: { spam_log_id: spam_log.id, 'g-recaptcha-response': true }) } .not_to change { SpamLog.last.recaptcha_verified } end end @@ -1298,7 +1314,7 @@ RSpec.describe Projects::IssuesController do let!(:last_spam_log) { spam_logs.last } def post_verified_issue - post_new_issue({}, { spam_log_id: last_spam_log.id, recaptcha_verification: true } ) + post_new_issue({}, { spam_log_id: last_spam_log.id, 'g-recaptcha-response': true } ) end before do @@ -1316,7 +1332,7 @@ RSpec.describe Projects::IssuesController do it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do spam_log = create(:spam_log) - expect { post_new_issue({}, { spam_log_id: spam_log.id, recaptcha_verification: true } ) } + expect { post_new_issue({}, { spam_log_id: spam_log.id, 'g-recaptcha-response': true } ) } .not_to change { last_spam_log.recaptcha_verified } end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index eb5e62f3d44..430808e1c63 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -675,16 +675,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:forbidden) end - - context 'with restrict_access_to_build_debug_mode feature disabled' do - before do - stub_feature_flags(restrict_access_to_build_debug_mode: false) - end - - it 'returns response forbidden' do - expect(response).to have_gitlab_http_status(:ok) - end - end end end end @@ -1139,18 +1129,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) end - - context 'with restrict_access_to_build_debug_mode feature disabled' do - before do - stub_feature_flags(restrict_access_to_build_debug_mode: false) - end - - it 'returns response ok' do - response = subject - - expect(response).to have_gitlab_http_status(:ok) - end - end end context 'without proper permissions for debug logging on a project' do @@ -1164,18 +1142,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:forbidden) end - - context 'with restrict_access_to_build_debug_mode feature disabled' do - before do - stub_feature_flags(restrict_access_to_build_debug_mode: false) - end - - it 'returns response ok' do - response = subject - - expect(response).to have_gitlab_http_status(:ok) - end - end end end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 8a3c55033cb..f452c22a5ca 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -84,46 +84,12 @@ RSpec.describe Projects::LabelsController do 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 + it 'returns ancestor group labels', :aggregate_failures do + params = { namespace_id: project.namespace.to_param, project_id: project } + 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' + 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 diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index f4f0a9f8108..f54a07de853 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -193,6 +193,29 @@ RSpec.describe Projects::MergeRequests::DiffsController do end end + context "with the :default_merge_ref_for_diffs flag on" do + let(:diffable_merge_ref) { true } + + subject do + go(diff_head: true, + diff_id: merge_request.merge_request_diff.id, + start_sha: merge_request.merge_request_diff.start_commit_sha) + end + + it "correctly generates the right diff between versions" do + MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request) + + expect_next_instance_of(CompareService) do |service| + expect(service).to receive(:execute).with( + project, + merge_request.merge_request_diff.head_commit_sha, + straight: true) + end + + subject + end + end + context 'with diff_head param passed' do before do allow(merge_request).to receive(:diffable_merge_ref?) @@ -378,6 +401,57 @@ RSpec.describe Projects::MergeRequests::DiffsController do expect(response).to have_gitlab_http_status(:ok) end + + it 'tracks mr_diffs event' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_mr_diffs_action) + .with(merge_request: merge_request) + + subject + end + + context 'when DNT is enabled' do + before do + request.headers['DNT'] = '1' + end + + it 'does not track any mr_diffs event' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_mr_diffs_action) + + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_mr_diffs_single_file_action) + + subject + end + end + + context 'when user has view_diffs_file_by_file set to false' do + before do + user.update!(view_diffs_file_by_file: false) + end + + it 'does not track single_file_diffs events' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_mr_diffs_single_file_action) + + subject + end + end + + context 'when user has view_diffs_file_by_file set to true' do + before do + user.update!(view_diffs_file_by_file: true) + end + + it 'tracks single_file_diffs events' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_mr_diffs_single_file_action) + .with(merge_request: merge_request, user: user) + + subject + end + end end def collection_arguments(pagination_data = {}) diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index e1405660ccb..be4a1504fc9 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -272,6 +272,72 @@ RSpec.describe Projects::PipelinesController do end end + describe 'GET #index' do + subject(:request) { get :index, params: { namespace_id: project.namespace, project_id: project } } + + context 'experiment not active' do + it 'does not push tracking_data to gon' do + request + + expect(Gon.tracking_data).to be_nil + end + + it 'does not record experiment_user' do + expect { request }.not_to change(ExperimentUser, :count) + end + end + + context 'when experiment active' do + before do + stub_experiment(pipelines_empty_state: true) + stub_experiment_for_subject(pipelines_empty_state: true) + end + + it 'pushes tracking_data to Gon' do + request + + expect(Gon.experiments["pipelinesEmptyState"]).to eq(true) + expect(Gon.tracking_data).to match( + { + category: 'Growth::Activation::Experiment::PipelinesEmptyState', + action: 'view', + label: anything, + property: 'experimental_group', + value: anything + } + ) + end + + context 'no pipelines created an no CI set up' do + before do + stub_application_setting(auto_devops_enabled: false) + end + + it 'records experiment_user' do + expect { request }.to change(ExperimentUser, :count).by(1) + end + end + + context 'CI set up' do + it 'does not record experiment_user' do + expect { request }.not_to change(ExperimentUser, :count) + end + end + + context 'pipelines created' do + let!(:pipeline) { create(:ci_pipeline, project: project) } + + before do + stub_application_setting(auto_devops_enabled: false) + end + + it 'does not record experiment_user' do + expect { request }.not_to change(ExperimentUser, :count) + end + end + end + end + describe 'GET show.json' do let(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 74311fa89f3..971eb782fa4 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -14,32 +14,137 @@ RSpec.describe Projects::ProjectMembersController do expect(response).to have_gitlab_http_status(:ok) end - context 'when project belongs to group' do - let(:user_in_group) { create(:user) } - let(:project_in_group) { create(:project, :public, group: group) } + context 'project members' do + context 'when project belongs to group' do + let(:user_in_group) { create(:user) } + let(:project_in_group) { create(:project, :public, group: group) } + + before do + group.add_owner(user_in_group) + project_in_group.add_maintainer(user) + sign_in(user) + end + + it 'lists inherited project members by default' do + get :index, params: { namespace_id: project_in_group.namespace, project_id: project_in_group } + + expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user.id, user_in_group.id) + end + + it 'lists direct project members only' do + get :index, params: { namespace_id: project_in_group.namespace, project_id: project_in_group, with_inherited_permissions: 'exclude' } + + expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user.id) + end + + it 'lists inherited project members only' do + get :index, params: { namespace_id: project_in_group.namespace, project_id: project_in_group, with_inherited_permissions: 'only' } + + expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user_in_group.id) + end + end + + context 'when invited members are present' do + let!(:invited_member) { create(:project_member, :invited, project: project) } + + before do + project.add_maintainer(user) + sign_in(user) + end + + it 'excludes the invited members from project members list' do + get :index, params: { namespace_id: project.namespace, project_id: project } + + expect(assigns(:project_members).map(&:invite_email)).not_to contain_exactly(invited_member.invite_email) + end + end + end + + context 'group links' do + let!(:project_group_link) { create(:project_group_link, project: project, group: group) } + + it 'lists group links' do + get :index, params: { namespace_id: project.namespace, project_id: project } + + expect(assigns(:group_links).map(&:id)).to contain_exactly(project_group_link.id) + end + + context 'when `search_groups` param is present' do + let(:group_2) { create(:group, :public, name: 'group_2') } + let!(:project_group_link_2) { create(:project_group_link, project: project, group: group_2) } + + it 'lists group links that match search' do + get :index, params: { namespace_id: project.namespace, project_id: project, search_groups: 'group_2' } + + expect(assigns(:group_links).map(&:id)).to contain_exactly(project_group_link_2.id) + end + end + end + + context 'invited members' do + let!(:invited_member) { create(:project_member, :invited, project: project) } before do - group.add_owner(user_in_group) - project_in_group.add_maintainer(user) + project.add_maintainer(user) sign_in(user) end - it 'lists inherited project members by default' do - get :index, params: { namespace_id: project_in_group.namespace, project_id: project_in_group } + context 'when user has `admin_project_member` permissions' do + before do + allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(true) + end + + it 'lists invited members' do + get :index, params: { namespace_id: project.namespace, project_id: project } + + expect(assigns(:invited_members).map(&:invite_email)).to contain_exactly(invited_member.invite_email) + end + end + + context 'when user does not have `admin_project_member` permissions' do + before do + allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(false) + end + + it 'does not list invited members' do + get :index, params: { namespace_id: project.namespace, project_id: project } + + expect(assigns(:invited_members)).to be_nil + end + end + end + + context 'access requests' do + let(:access_requester_user) { create(:user) } - expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user.id, user_in_group.id) + before do + project.request_access(access_requester_user) + project.add_maintainer(user) + sign_in(user) end - it 'lists direct project members only' do - get :index, params: { namespace_id: project_in_group.namespace, project_id: project_in_group, with_inherited_permissions: 'exclude' } + context 'when user has `admin_project_member` permissions' do + before do + allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(true) + end + + it 'lists access requests' do + get :index, params: { namespace_id: project.namespace, project_id: project } - expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user.id) + expect(assigns(:requesters).map(&:user_id)).to contain_exactly(access_requester_user.id) + end end - it 'lists inherited project members only' do - get :index, params: { namespace_id: project_in_group.namespace, project_id: project_in_group, with_inherited_permissions: 'only' } + context 'when user does not have `admin_project_member` permissions' do + before do + allow(controller.helpers).to receive(:can_manage_project_members?).with(project).and_return(false) + end + + it 'does not list access requests' do + get :index, params: { namespace_id: project.namespace, project_id: project } - expect(assigns(:project_members).map(&:user_id)).to contain_exactly(user_in_group.id) + expect(assigns(:requesters)).to be_nil + end end end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index bd7ef3db8b6..a611ac16cd9 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -211,28 +211,13 @@ RSpec.describe ProjectsController do end end - context 'when the storage is not available', :broken_storage do - let_it_be(:project) { create(:project, :broken_storage) } - - before do - project.add_developer(user) - sign_in(user) - end - - it 'renders a 503' do - get :show, params: { namespace_id: project.namespace, id: project } - - expect(response).to have_gitlab_http_status(:service_unavailable) - end - end - context "project with empty repo" do let_it_be(:empty_project) { create(:project_empty_repo, :public) } before do sign_in(user) - allow(controller).to receive(:record_experiment_user).with(:invite_members_empty_project_version_a) + allow(controller).to receive(:record_experiment_user) end User.project_views.keys.each do |project_view| @@ -498,14 +483,14 @@ RSpec.describe ProjectsController do describe '#housekeeping' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } - let(:housekeeping) { Projects::HousekeepingService.new(project) } + let(:housekeeping) { Repositories::HousekeepingService.new(project) } context 'when authenticated as owner' do before do group.add_owner(user) sign_in(user) - allow(Projects::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping) + allow(Repositories::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping) end it 'forces a full garbage collection' do @@ -616,7 +601,7 @@ RSpec.describe ProjectsController do expect { update_project path: 'renamed_path' } .not_to change { project.reload.path } - expect(controller).to set_flash.now[:alert].to(s_('UpdateProject|Cannot rename project because it contains container registry tags!')) + expect(controller).to set_flash[:alert].to(s_('UpdateProject|Cannot rename project because it contains container registry tags!')) expect(response).to have_gitlab_http_status(:ok) end end @@ -748,7 +733,7 @@ RSpec.describe ProjectsController do describe '#transfer', :enable_admin_mode do render_views - let_it_be(:project, reload: true) { create(:project, :repository) } + let_it_be(:project, reload: true) { create(:project) } let_it_be(:admin) { create(:admin) } let_it_be(:new_namespace) { create(:namespace) } diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 2fb17e56f37..737ec4f95c5 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -6,7 +6,6 @@ RSpec.describe RegistrationsController do include TermsHelper before do - stub_feature_flags(invisible_captcha: false) stub_application_setting(require_admin_approval_after_user_signup: false) end @@ -193,15 +192,10 @@ RSpec.describe RegistrationsController do context 'when invisible captcha is enabled' do before do - stub_feature_flags(invisible_captcha: true) - InvisibleCaptcha.timestamp_enabled = true + stub_application_setting(invisible_captcha_enabled: true) InvisibleCaptcha.timestamp_threshold = treshold end - after do - InvisibleCaptcha.timestamp_enabled = false - end - let(:treshold) { 4 } let(:session_params) { { invisible_captcha_timestamp: form_rendered_time.iso8601 } } let(:form_rendered_time) { Time.current } diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb index 551abf9241d..34052496871 100644 --- a/spec/controllers/repositories/git_http_controller_spec.rb +++ b/spec/controllers/repositories/git_http_controller_spec.rb @@ -52,7 +52,7 @@ RSpec.describe Repositories::GitHttpController do }.from(0).to(1) end - it 'records a namespace onboarding progress action' do + it 'records an onboarding progress action' do expect_next_instance_of(OnboardingProgressService) do |service| expect(service).to receive(:execute).with(action: :git_read) end diff --git a/spec/controllers/runner_setup_controller_spec.rb b/spec/controllers/runner_setup_controller_spec.rb deleted file mode 100644 index 0b237500907..00000000000 --- a/spec/controllers/runner_setup_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# 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/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb deleted file mode 100644 index 916befe3f62..00000000000 --- a/spec/controllers/users_controller_spec.rb +++ /dev/null @@ -1,925 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe UsersController do - # This user should have the same e-mail address associated with the GPG key prepared for tests - let(:user) { create(:user, email: GpgHelpers::User1.emails[0]) } - let(:private_user) { create(:user, private_profile: true) } - let(:public_user) { create(:user) } - - describe 'GET #show' do - context 'with rendered views' do - render_views - - describe 'when logged in' do - before do - sign_in(user) - end - - it 'renders the show template' do - get :show, params: { username: user.username } - - expect(response).to be_successful - expect(response).to render_template('show') - end - end - - describe 'when logged out' do - it 'renders the show template' do - get :show, params: { username: user.username } - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('show') - end - end - end - - context 'when public visibility level is restricted' do - before do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end - - context 'when logged out' do - it 'redirects to login page' do - get :show, params: { username: user.username } - expect(response).to redirect_to new_user_session_path - end - end - - context 'when logged in' do - before do - sign_in(user) - end - - it 'renders show' do - get :show, params: { username: user.username } - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('show') - end - end - end - - context 'when a user by that username does not exist' do - context 'when logged out' do - it 'redirects to login page' do - get :show, params: { username: 'nonexistent' } - expect(response).to redirect_to new_user_session_path - end - end - - context 'when logged in' do - before do - sign_in(user) - end - - it 'renders 404' do - get :show, params: { username: 'nonexistent' } - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - context 'json with events' do - let(:project) { create(:project) } - - before do - project.add_developer(user) - Gitlab::DataBuilder::Push.build_sample(project, user) - - sign_in(user) - end - - it 'loads events' do - get :show, params: { username: user }, format: :json - - expect(assigns(:events)).not_to be_empty - end - - it 'hides events if the user cannot read cross project' do - allow(Ability).to receive(:allowed?).and_call_original - expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - - get :show, params: { username: user }, format: :json - - expect(assigns(:events)).to be_empty - end - - it 'hides events if the user has a private profile' do - Gitlab::DataBuilder::Push.build_sample(project, private_user) - - get :show, params: { username: private_user.username }, format: :json - - expect(assigns(:events)).to be_empty - end - end - end - - describe 'GET #activity' do - context 'with rendered views' do - render_views - - describe 'when logged in' do - before do - sign_in(user) - end - - it 'renders the show template' do - get :show, params: { username: user.username } - - expect(response).to be_successful - expect(response).to render_template('show') - end - end - - describe 'when logged out' do - it 'renders the show template' do - get :activity, params: { username: user.username } - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('show') - end - end - end - - context 'when public visibility level is restricted' do - before do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end - - context 'when logged out' do - it 'redirects to login page' do - get :activity, params: { username: user.username } - expect(response).to redirect_to new_user_session_path - end - end - - context 'when logged in' do - before do - sign_in(user) - end - - it 'renders show' do - get :activity, params: { username: user.username } - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('show') - end - end - end - - context 'when a user by that username does not exist' do - context 'when logged out' do - it 'redirects to login page' do - get :activity, params: { username: 'nonexistent' } - expect(response).to redirect_to new_user_session_path - end - end - - context 'when logged in' do - before do - sign_in(user) - end - - it 'renders 404' do - get :activity, params: { username: 'nonexistent' } - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - context 'json with events' do - let(:project) { create(:project) } - - before do - project.add_developer(user) - Gitlab::DataBuilder::Push.build_sample(project, user) - - sign_in(user) - end - - it 'loads events' do - get :activity, params: { username: user }, format: :json - - expect(assigns(:events)).not_to be_empty - end - - it 'hides events if the user cannot read cross project' do - allow(Ability).to receive(:allowed?).and_call_original - expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - - get :activity, params: { username: user }, format: :json - - expect(assigns(:events)).to be_empty - end - - it 'hides events if the user has a private profile' do - Gitlab::DataBuilder::Push.build_sample(project, private_user) - - get :activity, params: { username: private_user.username }, format: :json - - expect(assigns(:events)).to be_empty - end - end - end - - describe "#ssh_keys" do - describe "non existent user" do - it "does not generally work" do - get :ssh_keys, params: { username: 'not-existent' } - - expect(response).not_to be_successful - end - end - - describe "user with no keys" do - it "does generally work" do - get :ssh_keys, params: { username: user.username } - - expect(response).to be_successful - end - - it "renders all keys separated with a new line" do - get :ssh_keys, params: { username: user.username } - - expect(response.body).to eq("") - end - - it "responds with text/plain content type" do - get :ssh_keys, params: { username: user.username } - expect(response.content_type).to eq("text/plain") - end - end - - describe "user with keys" do - let!(:key) { create(:key, user: user) } - let!(:another_key) { create(:another_key, user: user) } - let!(:deploy_key) { create(:deploy_key, user: user) } - - describe "while signed in" do - before do - sign_in(user) - end - - it "does generally work" do - get :ssh_keys, params: { username: user.username } - - expect(response).to be_successful - end - - it "renders all non deploy keys separated with a new line" do - get :ssh_keys, params: { username: user.username } - - expect(response.body).not_to eq('') - expect(response.body).to eq(user.all_ssh_keys.join("\n")) - - expect(response.body).to include(key.key.sub(' dummy@gitlab.com', '')) - expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', '')) - - expect(response.body).not_to include(deploy_key.key) - end - - it "does not render the comment of the key" do - get :ssh_keys, params: { username: user.username } - expect(response.body).not_to match(/dummy@gitlab.com/) - end - - it "responds with text/plain content type" do - get :ssh_keys, params: { username: user.username } - - expect(response.content_type).to eq("text/plain") - end - end - - describe 'when logged out' do - before do - sign_out(user) - end - - it "still does generally work" do - get :ssh_keys, params: { username: user.username } - - expect(response).to be_successful - end - - it "renders all non deploy keys separated with a new line" do - get :ssh_keys, params: { username: user.username } - - expect(response.body).not_to eq('') - expect(response.body).to eq(user.all_ssh_keys.join("\n")) - - expect(response.body).to include(key.key.sub(' dummy@gitlab.com', '')) - expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', '')) - - expect(response.body).not_to include(deploy_key.key) - end - - it "does not render the comment of the key" do - get :ssh_keys, params: { username: user.username } - expect(response.body).not_to match(/dummy@gitlab.com/) - end - - it "responds with text/plain content type" do - get :ssh_keys, params: { username: user.username } - - expect(response.content_type).to eq("text/plain") - end - end - end - end - - describe "#gpg_keys" do - describe "non existent user" do - it "does not generally work" do - get :gpg_keys, params: { username: 'not-existent' } - - expect(response).not_to be_successful - end - end - - describe "user with no keys" do - it "does generally work" do - get :gpg_keys, params: { username: user.username } - - expect(response).to be_successful - end - - it "renders all keys separated with a new line" do - get :gpg_keys, params: { username: user.username } - - expect(response.body).to eq("") - end - - it "responds with text/plain content type" do - get :gpg_keys, params: { username: user.username } - - expect(response.content_type).to eq("text/plain") - end - end - - describe "user with keys" do - let!(:gpg_key) { create(:gpg_key, user: user) } - let!(:another_gpg_key) { create(:another_gpg_key, user: user) } - - describe "while signed in" do - before do - sign_in(user) - end - - it "does generally work" do - get :gpg_keys, params: { username: user.username } - - expect(response).to be_successful - end - - it "renders all verified keys separated with a new line" do - get :gpg_keys, params: { username: user.username } - - expect(response.body).not_to eq('') - expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n")) - - expect(response.body).to include(gpg_key.key) - expect(response.body).to include(another_gpg_key.key) - end - - it "responds with text/plain content type" do - get :gpg_keys, params: { username: user.username } - - expect(response.content_type).to eq("text/plain") - end - end - - describe 'when logged out' do - before do - sign_out(user) - end - - it "still does generally work" do - get :gpg_keys, params: { username: user.username } - - expect(response).to be_successful - end - - it "renders all verified keys separated with a new line" do - get :gpg_keys, params: { username: user.username } - - expect(response.body).not_to eq('') - expect(response.body).to eq(user.gpg_keys.map(&:key).join("\n")) - - expect(response.body).to include(gpg_key.key) - expect(response.body).to include(another_gpg_key.key) - end - - it "responds with text/plain content type" do - get :gpg_keys, params: { username: user.username } - - expect(response.content_type).to eq("text/plain") - end - end - - describe 'when revoked' do - before do - sign_in(user) - another_gpg_key.revoke - end - - it "doesn't render revoked keys" do - get :gpg_keys, params: { username: user.username } - - expect(response.body).not_to eq('') - - expect(response.body).to include(gpg_key.key) - expect(response.body).not_to include(another_gpg_key.key) - end - - it "doesn't render revoked keys for non-authorized users" do - sign_out(user) - get :gpg_keys, params: { username: user.username } - - expect(response.body).not_to eq('') - - expect(response.body).to include(gpg_key.key) - expect(response.body).not_to include(another_gpg_key.key) - end - end - end - end - - describe 'GET #calendar' do - context 'for user' do - let(:project) { create(:project) } - - before do - sign_in(user) - project.add_developer(user) - end - - context 'with public profile' do - it 'renders calendar' do - push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) - EventCreateService.new.push(project, public_user, push_data) - - get :calendar, params: { username: public_user.username }, format: :json - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'with private profile' do - it 'does not render calendar' do - push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user) - EventCreateService.new.push(project, private_user, push_data) - - get :calendar, params: { username: private_user.username }, format: :json - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - context 'forked project' do - let(:project) { create(:project) } - let(:forked_project) { Projects::ForkService.new(project, user).execute } - - before do - sign_in(user) - project.add_developer(user) - - push_data = Gitlab::DataBuilder::Push.build_sample(project, user) - - fork_push_data = Gitlab::DataBuilder::Push - .build_sample(forked_project, user) - - EventCreateService.new.push(project, user, push_data) - EventCreateService.new.push(forked_project, user, fork_push_data) - end - - it 'includes forked projects' do - get :calendar, params: { username: user.username } - expect(assigns(:contributions_calendar).projects.count).to eq(2) - end - end - end - - describe 'GET #calendar_activities' do - let!(:project) { create(:project) } - let(:user) { create(:user) } - - before do - allow_next_instance_of(User) do |instance| - allow(instance).to receive(:contributed_projects_ids).and_return([project.id]) - end - - sign_in(user) - project.add_developer(user) - end - - it 'assigns @calendar_date' do - get :calendar_activities, params: { username: user.username, date: '2014-07-31' } - expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) - end - - context 'for user' do - context 'with public profile' do - let(:issue) { create(:issue, project: project, author: user) } - let(:note) { create(:note, noteable: issue, author: user, project: project) } - - render_views - - before do - create_push_event - create_note_event - end - - it 'renders calendar_activities' do - get :calendar_activities, params: { username: public_user.username } - - expect(assigns[:events]).not_to be_empty - end - - it 'avoids N+1 queries', :request_store do - get :calendar_activities, params: { username: public_user.username } - - control = ActiveRecord::QueryRecorder.new { get :calendar_activities, params: { username: public_user.username } } - - create_push_event - create_note_event - - expect { get :calendar_activities, params: { username: public_user.username } }.not_to exceed_query_limit(control) - end - end - - context 'with private profile' do - it 'does not render calendar_activities' do - push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user) - EventCreateService.new.push(project, private_user, push_data) - - get :calendar_activities, params: { username: private_user.username } - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'external authorization' do - subject { get :calendar_activities, params: { username: user.username } } - - it_behaves_like 'disabled when using an external authorization service' - end - - def create_push_event - push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user) - EventCreateService.new.push(project, public_user, push_data) - end - - def create_note_event - EventCreateService.new.leave_note(note, public_user) - end - end - end - - describe 'GET #contributed' do - let(:project) { create(:project, :public) } - - subject do - get :contributed, params: { username: author.username }, format: format - end - - before do - sign_in(user) - - project.add_developer(public_user) - project.add_developer(private_user) - create(:push_event, project: project, author: author) - - subject - end - - shared_examples_for 'renders contributed projects' do - it 'renders contributed projects' do - expect(assigns[:contributed_projects]).not_to be_empty - expect(response).to have_gitlab_http_status(:ok) - end - end - - %i(html json).each do |format| - context "format: #{format}" do - let(:format) { format } - - context 'with public profile' do - let(:author) { public_user } - - it_behaves_like 'renders contributed projects' - end - - context 'with private profile' do - let(:author) { private_user } - - it 'returns 404' do - expect(response).to have_gitlab_http_status(:not_found) - end - - context 'with a user that has the ability to read private profiles', :enable_admin_mode do - let(:user) { create(:admin) } - - it_behaves_like 'renders contributed projects' - end - end - end - end - end - - describe 'GET #starred' do - let(:project) { create(:project, :public) } - - subject do - get :starred, params: { username: author.username }, format: format - end - - before do - author.toggle_star(project) - - sign_in(user) - subject - end - - shared_examples_for 'renders starred projects' do - it 'renders starred projects' do - expect(response).to have_gitlab_http_status(:ok) - expect(assigns[:starred_projects]).not_to be_empty - end - end - - %i(html json).each do |format| - context "format: #{format}" do - let(:format) { format } - - context 'with public profile' do - let(:author) { public_user } - - it_behaves_like 'renders starred projects' - end - - context 'with private profile' do - let(:author) { private_user } - - it 'returns 404' do - expect(response).to have_gitlab_http_status(:not_found) - end - - context 'with a user that has the ability to read private profiles', :enable_admin_mode do - let(:user) { create(:admin) } - - it_behaves_like 'renders starred projects' - end - end - end - end - end - - describe 'GET #snippets' do - before do - sign_in(user) - end - - context 'format html' do - it 'renders snippets page' do - get :snippets, params: { username: user.username } - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template('show') - end - end - - context 'format json' do - it 'response with snippets json data' do - get :snippets, params: { username: user.username }, format: :json - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to have_key('html') - end - end - - context 'external authorization' do - subject { get :snippets, params: { username: user.username } } - - it_behaves_like 'disabled when using an external authorization service' - end - end - - describe 'GET #exists' do - before do - sign_in(user) - end - - context 'when user exists' do - it 'returns JSON indicating the user exists' do - get :exists, params: { username: user.username } - - expected_json = { exists: true }.to_json - expect(response.body).to eq(expected_json) - end - - context 'when the casing is different' do - let(:user) { create(:user, username: 'CamelCaseUser') } - - it 'returns JSON indicating the user exists' do - get :exists, params: { username: user.username.downcase } - - expected_json = { exists: true }.to_json - expect(response.body).to eq(expected_json) - end - end - end - - context 'when the user does not exist' do - it 'returns JSON indicating the user does not exist' do - get :exists, params: { username: 'foo' } - - expected_json = { exists: false }.to_json - expect(response.body).to eq(expected_json) - end - - context 'when a user changed their username' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') } - - it 'returns JSON indicating a user by that username does not exist' do - get :exists, params: { username: 'old-username' } - - expected_json = { exists: false }.to_json - expect(response.body).to eq(expected_json) - end - end - end - end - - describe 'GET #suggests' do - context 'when user exists' do - it 'returns JSON indicating the user exists and a suggestion' do - get :suggests, params: { username: user.username } - - expected_json = { exists: true, suggests: ["#{user.username}1"] }.to_json - expect(response.body).to eq(expected_json) - end - - context 'when the casing is different' do - let(:user) { create(:user, username: 'CamelCaseUser') } - - it 'returns JSON indicating the user exists and a suggestion' do - get :suggests, params: { username: user.username.downcase } - - expected_json = { exists: true, suggests: ["#{user.username.downcase}1"] }.to_json - expect(response.body).to eq(expected_json) - end - end - end - - context 'when the user does not exist' do - it 'returns JSON indicating the user does not exist' do - get :suggests, params: { username: 'foo' } - - expected_json = { exists: false, suggests: [] }.to_json - expect(response.body).to eq(expected_json) - end - - context 'when a user changed their username' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') } - - it 'returns JSON indicating a user by that username does not exist' do - get :suggests, params: { username: 'old-username' } - - expected_json = { exists: false, suggests: [] }.to_json - expect(response.body).to eq(expected_json) - end - end - end - end - - describe '#ensure_canonical_path' do - before do - sign_in(user) - end - - context 'for a GET request' do - context 'when requesting users at the root path' do - context 'when requesting the canonical path' do - let(:user) { create(:user, username: 'CamelCaseUser') } - - context 'with exactly matching casing' do - it 'responds with success' do - get :show, params: { username: user.username } - - expect(response).to be_successful - end - end - - context 'with different casing' do - it 'redirects to the correct casing' do - get :show, params: { username: user.username.downcase } - - expect(response).to redirect_to(user) - expect(controller).not_to set_flash[:notice] - end - end - end - - context 'when requesting a redirected path' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') } - - it 'redirects to the canonical path' do - get :show, params: { username: redirect_route.path } - - expect(response).to redirect_to(user) - expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) - end - - context 'when the old path is a substring of the scheme or host' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') } - - it 'does not modify the requested host' do - get :show, params: { username: redirect_route.path } - - expect(response).to redirect_to(user) - expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) - end - end - - context 'when the old path is substring of users' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'ser') } - - it 'redirects to the canonical path' do - get :show, params: { username: redirect_route.path } - - expect(response).to redirect_to(user) - expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) - end - end - end - end - - context 'when requesting users under the /users path' do - context 'when requesting the canonical path' do - let(:user) { create(:user, username: 'CamelCaseUser') } - - context 'with exactly matching casing' do - it 'responds with success' do - get :projects, params: { username: user.username } - - expect(response).to be_successful - end - end - - context 'with different casing' do - it 'redirects to the correct casing' do - get :projects, params: { username: user.username.downcase } - - expect(response).to redirect_to(user_projects_path(user)) - expect(controller).not_to set_flash[:notice] - end - end - end - - context 'when requesting a redirected path' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') } - - it 'redirects to the canonical path' do - get :projects, params: { username: redirect_route.path } - - expect(response).to redirect_to(user_projects_path(user)) - expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) - end - - context 'when the old path is a substring of the scheme or host' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') } - - it 'does not modify the requested host' do - get :projects, params: { username: redirect_route.path } - - expect(response).to redirect_to(user_projects_path(user)) - expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) - end - end - - context 'when the old path is substring of users' do - let(:redirect_route) { user.namespace.redirect_routes.create(path: 'ser') } - - # I.e. /users/ser should not become /ufoos/ser - it 'does not modify the /users part of the path' do - get :projects, params: { username: redirect_route.path } - - expect(response).to redirect_to(user_projects_path(user)) - expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user)) - end - end - end - end - end - end - - context 'token authentication' do - it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do - before do - default_params.merge!(username: user.username) - end - end - end - - def user_moved_message(redirect_route, user) - "User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path." - end -end |