diff options
Diffstat (limited to 'spec')
120 files changed, 2951 insertions, 844 deletions
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 84027119491..7fb3578cd0a 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -5,12 +5,19 @@ require 'spec_helper' describe MetricsController do include StubENV - let(:metrics_multiproc_dir) { Dir.mktmpdir } + let(:metrics_multiproc_dir) { @metrics_multiproc_dir } let(:whitelisted_ip) { '127.0.0.1' } let(:whitelisted_ip_range) { '10.0.0.0/24' } let(:ip_in_whitelisted_range) { '10.0.0.1' } let(:not_whitelisted_ip) { '10.0.1.1' } + around do |example| + Dir.mktmpdir do |path| + @metrics_multiproc_dir = path + example.run + end + end + before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir) diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 8872e8d38e7..b3852355d77 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -518,10 +518,10 @@ describe Projects::EnvironmentsController do end end - shared_examples_for 'the default dynamic dashboard' do + shared_examples_for 'specified dashboard embed' do |expected_titles| it_behaves_like '200 response' - it 'contains only the Memory and CPU charts' do + it 'contains only the specified charts' do get :metrics_dashboard, params: environment_params(dashboard_params) dashboard = json_response['dashboard'] @@ -531,10 +531,14 @@ describe Projects::EnvironmentsController do expect(dashboard['dashboard']).to be_nil expect(dashboard['panel_groups'].length).to eq 1 expect(panel_group['group']).to be_nil - expect(titles).to eq ['Memory Usage (Total)', 'Core Usage (Total)'] + expect(titles).to eq expected_titles end end + shared_examples_for 'the default dynamic dashboard' do + it_behaves_like 'specified dashboard embed', ['Memory Usage (Total)', 'Core Usage (Total)'] + end + shared_examples_for 'dashboard can be specified' do context 'when dashboard is specified' do let(:dashboard_path) { '.gitlab/dashboards/test.yml' } @@ -551,7 +555,7 @@ describe Projects::EnvironmentsController do end context 'when the specified dashboard is the default dashboard' do - let(:dashboard_path) { ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH } + let(:dashboard_path) { system_dashboard_path } it_behaves_like 'the default dashboard' end @@ -564,12 +568,40 @@ describe Projects::EnvironmentsController do it_behaves_like 'the default dynamic dashboard' - context 'when the dashboard is specified' do - let(:dashboard_params) { { format: :json, embedded: true, dashboard: '.gitlab/dashboards/fake.yml' } } + context 'when incomplete dashboard params are provided' do + let(:dashboard_params) { { format: :json, embedded: true, title: 'Title' } } + + # The title param should be ignored. + it_behaves_like 'the default dynamic dashboard' + end + + context 'when invalid params are provided' do + let(:dashboard_params) { { format: :json, embedded: true, metric_id: 16 } } - # The dashboard param should be ignored. + # The superfluous param should be ignored. it_behaves_like 'the default dynamic dashboard' end + + context 'when the dashboard is correctly specified' do + let(:dashboard_params) do + { + format: :json, + embedded: true, + dashboard: system_dashboard_path, + group: business_metric_title, + title: 'title', + y_label: 'y_label' + } + end + + it_behaves_like 'error response', :not_found + + context 'and exists' do + let!(:metric) { create(:prometheus_metric, project: project) } + + it_behaves_like 'specified dashboard embed', ['title'] + end + end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 0f885d776e1..fab47aa4701 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -251,15 +251,13 @@ describe Projects::IssuesController do end end - describe 'Redirect after sign in' do + # This spec runs as a request-style spec in order to invoke the + # 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 it 'does not store the visited URL' do - get :show, params: { - format: :json, - namespace_id: project.namespace, - project_id: project, - id: issue.iid - }, xhr: true + get project_issue_path(project, issue), xhr: true expect(session['user_return_to']).to be_blank end @@ -267,14 +265,9 @@ describe Projects::IssuesController do context 'without an AJAX request' do it 'stores the visited URL' do - get :show, - params: { - namespace_id: project.namespace.to_param, - project_id: project, - id: issue.iid - } + get project_issue_path(project, issue) - expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}") + expect(session['user_return_to']).to eq(project_issue_path(project, issue)) end end end diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 18c594acae0..9f1ef3a4be8 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -10,12 +10,16 @@ describe Projects::Serverless::FunctionsController do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { cluster.platform_kubernetes } let(:project) { cluster.project } + let(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } + let(:knative_services_finder) { environment.knative_services_finder } let(:namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) + project: cluster.cluster_project.project, + environment: environment) end before do @@ -47,12 +51,11 @@ describe Projects::Serverless::FunctionsController do end context 'when cache is ready' do - let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } let(:knative_state) { true } before do - allow_any_instance_of(Clusters::Cluster) - .to receive(:knative_services_finder) + allow(Clusters::KnativeServicesFinder) + .to receive(:new) .and_return(knative_services_finder) synchronous_reactive_cache(knative_services_finder) stub_kubeclient_service_pods( @@ -107,12 +110,12 @@ describe Projects::Serverless::FunctionsController do context 'valid data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods - stub_reactive_cache(cluster.knative_services_finder(project), + stub_reactive_cache(knative_services_finder, { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] }, - *cluster.knative_services_finder(project).cache_args) + *knative_services_finder.cache_args) end it 'has a valid function name' do @@ -140,12 +143,12 @@ describe Projects::Serverless::FunctionsController do describe 'GET #index with data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods - stub_reactive_cache(cluster.knative_services_finder(project), + stub_reactive_cache(knative_services_finder, { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] }, - *cluster.knative_services_finder(project).cache_args) + *knative_services_finder.cache_args) end it 'has data' do diff --git a/spec/controllers/projects/starrers_controller_spec.rb b/spec/controllers/projects/starrers_controller_spec.rb new file mode 100644 index 00000000000..59d258e99ce --- /dev/null +++ b/spec/controllers/projects/starrers_controller_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::StarrersController do + let(:user) { create(:user) } + let(:private_user) { create(:user, private_profile: true) } + let(:admin) { create(:user, admin: true) } + let(:project) { create(:project, :public, :repository) } + + before do + user.toggle_star(project) + private_user.toggle_star(project) + end + + describe 'GET index' do + def get_starrers + get :index, + params: { + namespace_id: project.namespace, + project_id: project + } + end + + context 'when project is public' do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context 'when no user is logged in' do + before do + get_starrers + end + + it 'only public starrers are visible' do + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id) + expect(user_ids).not_to include(private_user.id) + end + + it 'public/private starrers counts are correct' do + expect(assigns[:public_count]).to eq(1) + expect(assigns[:private_count]).to eq(1) + end + end + + context 'when private user is logged in' do + before do + sign_in(private_user) + + get_starrers + end + + it 'their star is also visible' do + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id, private_user.id) + end + + it 'public/private starrers counts are correct' do + expect(assigns[:public_count]).to eq(1) + expect(assigns[:private_count]).to eq(1) + end + end + + context 'when admin is logged in' do + before do + sign_in(admin) + + get_starrers + end + + it 'all stars are visible' do + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id, private_user.id) + end + + it 'public/private starrers counts are correct' do + expect(assigns[:public_count]).to eq(1) + expect(assigns[:private_count]).to eq(1) + end + end + end + + context 'when project is private' do + before do + project.update(visibility_level: Project::PRIVATE) + end + + it 'starrers are not visible for non logged in users' do + get_starrers + + expect(assigns[:starrers]).to be_blank + end + + context 'when user is logged in' do + before do + sign_in(project.creator) + end + + it 'only public starrers are visible' do + get_starrers + + user_ids = assigns[:starrers].map { |s| s['user_id'] } + expect(user_ids).to include(user.id) + expect(user_ids).not_to include(private_user.id) + end + end + end + end +end diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 24c22ef3928..89f7bc15217 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -4,6 +4,20 @@ FactoryBot.define do factory :clusters_applications_helm, class: Clusters::Applications::Helm do cluster factory: %i(cluster provided_by_gcp) + before(:create) do + allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root) + .and_return( + double( + key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')), + cert_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) + ) + ) + end + + after(:create) do + allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root).and_call_original + end + trait :not_installable do status(-2) end diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb index b0d14b672f4..d294e6d055e 100644 --- a/spec/factories/clusters/clusters.rb +++ b/spec/factories/clusters/clusters.rb @@ -6,6 +6,7 @@ FactoryBot.define do name 'test-cluster' cluster_type :project_type managed true + namespace_per_environment true factory :cluster_for_group, traits: [:provided_by_gcp, :group] @@ -29,6 +30,10 @@ FactoryBot.define do end end + trait :namespace_per_environment_disabled do + namespace_per_environment false + end + trait :provided_by_user do provider_type :user platform_type :kubernetes diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb index 042be7b4c4a..8d6ad1b9f79 100644 --- a/spec/factories/clusters/kubernetes_namespaces.rb +++ b/spec/factories/clusters/kubernetes_namespaces.rb @@ -5,12 +5,21 @@ FactoryBot.define do association :cluster, :project, :provided_by_gcp after(:build) do |kubernetes_namespace| - if kubernetes_namespace.cluster.project_type? - cluster_project = kubernetes_namespace.cluster.cluster_project + cluster = kubernetes_namespace.cluster + + if cluster.project_type? + cluster_project = cluster.cluster_project kubernetes_namespace.project = cluster_project.project kubernetes_namespace.cluster_project = cluster_project end + + kubernetes_namespace.namespace ||= + Gitlab::Kubernetes::DefaultNamespace.new( + cluster, + project: kubernetes_namespace.project + ).from_environment_slug(kubernetes_namespace.environment&.slug) + kubernetes_namespace.service_account_name ||= "#{kubernetes_namespace.namespace}-service-account" end trait :with_token do diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index c77605f3869..ddd87404003 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -338,14 +338,17 @@ describe 'Admin updates settings' do visit network_admin_application_settings_path page.within('.as-outbound') do - check 'Allow requests to the local network from hooks and services' + check 'Allow requests to the local network from web hooks and services' + # Enabled by default + uncheck 'Allow requests to the local network from system hooks' # Enabled by default uncheck 'Enforce DNS rebinding attack protection' click_button 'Save changes' end expect(page).to have_content "Application settings saved successfully" - expect(current_settings.allow_local_requests_from_hooks_and_services).to be true + expect(current_settings.allow_local_requests_from_web_hooks_and_services).to be true + expect(current_settings.allow_local_requests_from_system_hooks).to be false expect(current_settings.dns_rebinding_protection_enabled).to be false end end diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index d2d084c9174..9c17aac09e8 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -19,9 +19,9 @@ describe 'Search group member' do end it 'renders member users' do - page.within '.member-search-form' do + page.within '.user-search-form' do fill_in 'search', with: member.name - find('.member-search-btn').click + find('.user-search-btn').click end group_members_list = find(".card .content-list") diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb index 11770e6ac2a..48b0136227e 100644 --- a/spec/features/groups/members/sort_members_spec.rb +++ b/spec/features/groups/members/sort_members_spec.rb @@ -19,7 +19,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by access level ascending' do @@ -27,7 +27,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') end it 'sorts by access level descending' do @@ -35,7 +35,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') end it 'sorts by last joined' do @@ -43,7 +43,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') end it 'sorts by oldest joined' do @@ -51,7 +51,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') end it 'sorts by name ascending' do @@ -59,7 +59,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by name descending' do @@ -67,7 +67,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do @@ -75,7 +75,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(owner.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do @@ -83,7 +83,7 @@ describe 'Groups > Members > Sort members' do expect(first_member).to include(developer.name) expect(second_member).to include(owner.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') end def visit_members_list(sort:) diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index 8cfd23d16df..3d15095e2da 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -183,7 +183,7 @@ describe 'Clusters Applications', :js do Clusters::Cluster.last.application_cert_manager.make_installed! expect(email_form_value).to eq('new_email@example.org') - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed') + expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall') end expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster') diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb index 7b1fded1834..6e8d1a945e1 100644 --- a/spec/features/projects/members/groups_with_access_list_spec.rb +++ b/spec/features/projects/members/groups_with_access_list_spec.rb @@ -52,18 +52,18 @@ describe 'Projects > Members > Groups with access list', :js do context 'search in existing members (yes, this filters the groups list as well)' do it 'finds no results' do - page.within '.member-search-form' do + page.within '.user-search-form' do fill_in 'search', with: 'testing 123' - find('.member-search-btn').click + find('.user-search-btn').click end expect(page).not_to have_selector('.group_member') end it 'finds results' do - page.within '.member-search-form' do + page.within '.user-search-form' do fill_in 'search', with: group.name - find('.member-search-btn').click + find('.user-search-btn').click end expect(page).to have_selector('.group_member', count: 1) diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index 332f07614da..88240fbbedc 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -18,7 +18,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by access level ascending' do @@ -26,7 +26,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending') end it 'sorts by access level descending' do @@ -34,7 +34,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending') end it 'sorts by last joined' do @@ -42,7 +42,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Last joined') end it 'sorts by oldest joined' do @@ -50,7 +50,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined') end it 'sorts by name ascending' do @@ -58,7 +58,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending') end it 'sorts by name descending' do @@ -66,7 +66,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do @@ -74,7 +74,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(maintainer.name) expect(second_member).to include(developer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do @@ -82,7 +82,7 @@ describe 'Projects > Members > Sorting' do expect(first_member).to include(developer.name) expect(second_member).to include(maintainer.name) - expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') + expect(page).to have_css('.user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in') end def visit_members_list(sort:) diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb index 9865dbbfb3c..e82e5b81021 100644 --- a/spec/features/projects/serverless/functions_spec.rb +++ b/spec/features/projects/serverless/functions_spec.rb @@ -39,17 +39,19 @@ describe 'Functions', :js do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { cluster.platform_kubernetes } let(:project) { cluster.project } - let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } + let(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, cluster: cluster, environment: environment) } + let(:knative_services_finder) { environment.knative_services_finder } let(:namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) + project: cluster.cluster_project.project, + environment: environment) end before do - allow_any_instance_of(Clusters::Cluster) - .to receive(:knative_services_finder) + allow(Clusters::KnativeServicesFinder) + .to receive(:new) .and_return(knative_services_finder) synchronous_reactive_cache(knative_services_finder) stub_kubeclient_knative_services(stub_get_services_options) diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb index 5803500a4d2..5f3bb794b48 100644 --- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -61,7 +61,7 @@ describe 'User activates issue tracker', :js do context 'when the connection test fails' do it 'activates the service' do - stub_request(:head, url).to_raise(HTTParty::Error) + stub_request(:head, url).to_raise(Gitlab::HTTP::Error) click_link(tracker) diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb index 67f0aa214e2..8fdeddfdfb4 100644 --- a/spec/features/projects/services/user_activates_youtrack_spec.rb +++ b/spec/features/projects/services/user_activates_youtrack_spec.rb @@ -48,7 +48,7 @@ describe 'User activates issue tracker', :js do context 'when the connection test fails' do it 'activates the service' do - stub_request(:head, url).to_raise(HTTParty::Error) + stub_request(:head, url).to_raise(Gitlab::HTTP::Error) click_link(tracker) fill_form diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb index 29ce5425323..c781048d06d 100644 --- a/spec/features/search/user_uses_header_search_field_spec.rb +++ b/spec/features/search/user_uses_header_search_field_spec.rb @@ -22,7 +22,7 @@ describe 'User uses header search field', :js do fill_in('search', with: 'gitlab') find('#search').native.send_keys(:enter) - page.within('.breadcrumbs-sub-title') do + page.within('.page-title') do expect(page).to have_content('Search') end end diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index f5cda15b38a..fbd7da3c643 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -22,7 +22,7 @@ describe 'User uses search filters', :js do wait_for_requests - page.within('.search-holder') do + page.within('.search-page-form') do click_link(group.name) end diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index 92e34a1f510..5ff12c37aff 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -34,7 +34,7 @@ describe 'User edits snippet', :js do click_button('Save changes') wait_for_requests - link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] + link = find('a.no-attachment-icon img:not(.lazy)[alt="banana_sample"]')['src'] expect(link).to match(%r{/uploads/-/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z}) end diff --git a/spec/features/user_opens_link_to_comment_spec.rb b/spec/features/user_opens_link_to_comment_spec.rb index f1e07e55799..9533a4fe40d 100644 --- a/spec/features/user_opens_link_to_comment_spec.rb +++ b/spec/features/user_opens_link_to_comment_spec.rb @@ -18,8 +18,13 @@ describe 'User opens link to comment', :js do visit Gitlab::UrlBuilder.build(note) + wait_for_requests + expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity') expect(page).not_to have_content('Something went wrong while fetching comments') + + # Auto-switching to show all notes shouldn't be persisted + expect(user.reload.notes_filter_for(note.noteable)).to eq(UserPreference::NOTES_FILTERS[:only_activity]) end end diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb index b731c2bd6bf..159724b3c1f 100644 --- a/spec/finders/clusters/knative_services_finder_spec.rb +++ b/spec/finders/clusters/knative_services_finder_spec.rb @@ -7,15 +7,19 @@ describe Clusters::KnativeServicesFinder do include ReactiveCachingHelpers let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:service) { cluster.platform_kubernetes } + let(:service) { environment.deployment_platform } let(:project) { cluster.cluster_project.project } + let(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } let(:namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, - cluster_project: cluster.cluster_project, - project: project) + project: project, + environment: environment) end + let(:finder) { described_class.new(cluster, environment) } + before do stub_kubeclient_knative_services(namespace: namespace.namespace) stub_kubeclient_service_pods( @@ -35,7 +39,7 @@ describe Clusters::KnativeServicesFinder do context 'when using synchronous reactive cache' do before do - synchronous_reactive_cache(cluster.knative_services_finder(project)) + synchronous_reactive_cache(finder) end context 'when there are functions for cluster namespace' do @@ -60,21 +64,21 @@ describe Clusters::KnativeServicesFinder do end describe '#service_pod_details' do - subject { cluster.knative_services_finder(project).service_pod_details(project.name) } + subject { finder.service_pod_details(project.name) } it_behaves_like 'a cached data' end describe '#services' do - subject { cluster.knative_services_finder(project).services } + subject { finder.services } it_behaves_like 'a cached data' end describe '#knative_detected' do - subject { cluster.knative_services_finder(project).knative_detected } + subject { finder.knative_detected } before do - synchronous_reactive_cache(cluster.knative_services_finder(project)) + synchronous_reactive_cache(finder) end context 'when knative is installed' do @@ -85,7 +89,7 @@ describe Clusters::KnativeServicesFinder do it { is_expected.to be_truthy } it "discovers knative installation" do expect { subject } - .to change { cluster.kubeclient.knative_client.discovered } + .to change { finder.cluster.kubeclient.knative_client.discovered } .from(false) .to(true) end diff --git a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb new file mode 100644 index 00000000000..8beba0b99a4 --- /dev/null +++ b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::KubernetesNamespaceFinder do + let(:finder) do + described_class.new( + cluster, + project: project, + environment_slug: 'production', + allow_blank_token: allow_blank_token + ) + end + + def create_namespace(environment, with_token: true) + create(:cluster_kubernetes_namespace, + (with_token ? :with_token : :without_token), + cluster: cluster, + project: project, + environment: environment + ) + end + + describe '#execute' do + let(:production) { create(:environment, project: project, slug: 'production') } + let(:staging) { create(:environment, project: project, slug: 'staging') } + + let(:cluster) { create(:cluster, :group, :provided_by_user) } + let(:project) { create(:project) } + let(:allow_blank_token) { false } + + subject { finder.execute } + + before do + allow(cluster).to receive(:namespace_per_environment?).and_return(namespace_per_environment) + end + + context 'cluster supports separate namespaces per environment' do + let(:namespace_per_environment) { true } + + context 'no persisted namespace is present' do + it { is_expected.to be_nil } + end + + context 'a namespace with an environment is present' do + context 'environment matches' do + let!(:namespace_with_environment) { create_namespace(production) } + + it { is_expected.to eq namespace_with_environment } + + context 'project cluster' do + let(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) } + + it { is_expected.to eq namespace_with_environment } + end + + context 'service account token is blank' do + let!(:namespace_with_environment) { create_namespace(production, with_token: false) } + + it { is_expected.to be_nil } + + context 'allow_blank_token is true' do + let(:allow_blank_token) { true } + + it { is_expected.to eq namespace_with_environment } + end + end + end + + context 'environment does not match' do + let!(:namespace_with_environment) { create_namespace(staging) } + + it { is_expected.to be_nil } + end + end + end + + context 'cluster does not support separate namespaces per environment' do + let(:namespace_per_environment) { false } + + context 'no persisted namespace is present' do + it { is_expected.to be_nil } + end + + context 'a legacy namespace with no environment is present' do + let!(:legacy_namespace) { create_namespace(nil) } + + it { is_expected.to eq legacy_namespace } + + context 'project cluster' do + let(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) } + + it { is_expected.to eq legacy_namespace } + end + + context 'service account token is blank' do + let!(:legacy_namespace) { create_namespace(nil, with_token: false) } + + it { is_expected.to be_nil } + + context 'allow_blank_token is true' do + let(:allow_blank_token) { true } + + it { is_expected.to eq legacy_namespace } + end + end + end + end + end +end diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb new file mode 100644 index 00000000000..deec62d6598 --- /dev/null +++ b/spec/finders/container_repositories_finder_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContainerRepositoriesFinder do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:project_repository) { create(:container_repository, project: project) } + + describe '#execute' do + let(:id) { nil } + + subject { described_class.new(id: id, container_type: container_type).execute } + + context 'when container_type is group' do + let(:other_project) { create(:project, group: group) } + + let(:other_repository) do + create(:container_repository, name: 'test_repository2', project: other_project) + end + + let(:container_type) { :group } + let(:id) { group.id } + + it { is_expected.to match_array([project_repository, other_repository]) } + end + + context 'when container_type is project' do + let(:container_type) { :project } + let(:id) { project.id } + + it { is_expected.to match_array([project_repository]) } + end + + context 'with invalid id' do + let(:container_type) { :project } + let(:id) { 123456789 } + + it 'raises an error' do + expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb index 8aea45b457c..589e4000d46 100644 --- a/spec/finders/projects/serverless/functions_finder_spec.rb +++ b/spec/finders/projects/serverless/functions_finder_spec.rb @@ -11,12 +11,15 @@ describe Projects::Serverless::FunctionsFinder do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { cluster.platform_kubernetes } let(:project) { cluster.project } + let(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } + let(:knative_services_finder) { environment.knative_services_finder } let(:namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, - cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) + project: project, + environment: environment) end before do @@ -29,11 +32,9 @@ describe Projects::Serverless::FunctionsFinder do end context 'when reactive_caching has finished' do - let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } - before do - allow_any_instance_of(Clusters::Cluster) - .to receive(:knative_services_finder) + allow(Clusters::KnativeServicesFinder) + .to receive(:new) .and_return(knative_services_finder) synchronous_reactive_cache(knative_services_finder) end @@ -47,8 +48,6 @@ describe Projects::Serverless::FunctionsFinder do end context 'reactive_caching is finished and knative is installed' do - let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } - it 'returns true' do stub_kubeclient_knative_services(namespace: namespace.namespace) stub_kubeclient_service_pods(nil, namespace: namespace.namespace) @@ -74,24 +73,24 @@ describe Projects::Serverless::FunctionsFinder do it 'there are functions', :use_clean_rails_memory_store_caching do stub_kubeclient_service_pods - stub_reactive_cache(cluster.knative_services_finder(project), + stub_reactive_cache(knative_services_finder, { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] }, - *cluster.knative_services_finder(project).cache_args) + *knative_services_finder.cache_args) expect(finder.execute).not_to be_empty end it 'has a function', :use_clean_rails_memory_store_caching do stub_kubeclient_service_pods - stub_reactive_cache(cluster.knative_services_finder(project), + stub_reactive_cache(knative_services_finder, { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] }, - *cluster.knative_services_finder(project).cache_args) + *knative_services_finder.cache_args) result = finder.service(cluster.environment_scope, cluster.project.name) expect(result).not_to be_empty @@ -109,7 +108,7 @@ describe Projects::Serverless::FunctionsFinder do let(:finder) { described_class.new(project) } before do - allow(finder).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(Prometheus::AdapterService).to receive(:new).and_return(double(prometheus_adapter: prometheus_adapter)) allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix')) end diff --git a/spec/finders/starred_projects_finder_spec.rb b/spec/finders/starred_projects_finder_spec.rb new file mode 100644 index 00000000000..7aa8251c3ab --- /dev/null +++ b/spec/finders/starred_projects_finder_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe StarredProjectsFinder do + let(:project1) { create(:project, :public, :empty_repo) } + let(:project2) { create(:project, :public, :empty_repo) } + let(:other_project) { create(:project, :public, :empty_repo) } + + let(:user) { create(:user) } + let(:other_user) { create(:user) } + + before do + user.toggle_star(project1) + user.toggle_star(project2) + end + + describe '#execute' do + let(:finder) { described_class.new(user, params: {}, current_user: current_user) } + + subject { finder.execute } + + describe 'as same user' do + let(:current_user) { user } + + it { is_expected.to contain_exactly(project1, project2) } + end + + describe 'as other user' do + let(:current_user) { other_user } + + it { is_expected.to contain_exactly(project1, project2) } + end + + describe 'as no user' do + let(:current_user) { nil } + + it { is_expected.to contain_exactly(project1, project2) } + end + end +end diff --git a/spec/finders/users_star_projects_finder_spec.rb b/spec/finders/users_star_projects_finder_spec.rb new file mode 100644 index 00000000000..fb1d8088f44 --- /dev/null +++ b/spec/finders/users_star_projects_finder_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UsersStarProjectsFinder do + let(:project) { create(:project, :public, :empty_repo) } + + let(:user) { create(:user) } + let(:private_user) { create(:user, private_profile: true) } + let(:other_user) { create(:user) } + + before do + user.toggle_star(project) + private_user.toggle_star(project) + end + + describe '#execute' do + let(:finder) { described_class.new(project, {}, current_user: current_user) } + let(:public_stars) { user.users_star_projects } + let(:private_stars) { private_user.users_star_projects } + + subject { finder.execute } + + describe 'as same user' do + let(:current_user) { private_user } + + it { is_expected.to match_array(private_stars + public_stars) } + end + + describe 'as other user' do + let(:current_user) { other_user } + + it { is_expected.to match_array(public_stars) } + end + + describe 'as no user' do + let(:current_user) { nil } + + it { is_expected.to match_array(public_stars) } + end + end +end diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json index e0fd4620c43..d0a068b65a7 100644 --- a/spec/fixtures/api/schemas/registry/repository.json +++ b/spec/fixtures/api/schemas/registry/repository.json @@ -17,6 +17,9 @@ "path": { "type": "string" }, + "project_id": { + "type": "integer" + }, "location": { "type": "string" }, @@ -28,7 +31,8 @@ }, "destroy_path": { "type": "string" - } + }, + "tags": { "$ref": "tags.json" } }, "additionalProperties": false } diff --git a/spec/fixtures/clusters/sample_key.key b/spec/fixtures/clusters/sample_key.key new file mode 100644 index 00000000000..4ddb20b0922 --- /dev/null +++ b/spec/fixtures/clusters/sample_key.key @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAMA5sXIBE0HwgIB40iNidN4PGWzOyLQK0bsdOBNgpEXkDlZBvnak +OUgAPF+rME4PB0Yl415DabUI40T5UNmlwxcCAwEAAQJAZtY2pSwIFm3JAXIh0cZZ +iXcAfiJ+YzuqinUOS+eW2sBCAEzjcARlU/o6sFQgtsOi4FOMczAd1Yx8UDMXMmrw +2QIhAPBgVhJiTF09pdmeFWutCvTJDlFFAQNbrbo2X2x/9WF9AiEAzLgqMKeStSRu +H9N16TuDrUoO8R+DPqriCwkKrSHaWyMCIFzMhE4inuKcSywBaLmiG4m3GQzs++Al +A6PRG/PSTpQtAiBxtBg6zdf+JC3GH3zt/dA0/10tL4OF2wORfYQghRzyYQIhAL2l +0ZQW+yLIZAGrdBFWYEAa52GZosncmzBNlsoTgwE4 +-----END RSA PRIVATE KEY-----
\ No newline at end of file diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json index 33393805464..9c1be32645a 100644 --- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json +++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json @@ -16,7 +16,8 @@ "unit": { "type": "string" }, "label": { "type": "string" }, "track": { "type": "string" }, - "prometheus_endpoint_path": { "type": "string" } + "prometheus_endpoint_path": { "type": "string" }, + "metric_id": { "type": "number" } }, "additionalProperties": false } diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index c771984a137..a986bc49f28 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -34,6 +34,41 @@ describe('URL utility', () => { }); }); + describe('getParameterValues', () => { + beforeEach(() => { + setWindowLocation({ + href: 'https://gitlab.com?test=passing&multiple=1&multiple=2', + // make our fake location act like real window.location.toString + // URL() (used in getParameterValues) does this if passed an object + toString() { + return this.href; + }, + }); + }); + + it('returns empty array for no params', () => { + expect(urlUtils.getParameterValues()).toEqual([]); + }); + + it('returns empty array for non-matching params', () => { + expect(urlUtils.getParameterValues('notFound')).toEqual([]); + }); + + it('returns single match', () => { + expect(urlUtils.getParameterValues('test')).toEqual(['passing']); + }); + + it('returns multiple matches', () => { + expect(urlUtils.getParameterValues('multiple')).toEqual(['1', '2']); + }); + + it('accepts url as second arg', () => { + const url = 'https://gitlab.com?everything=works'; + expect(urlUtils.getParameterValues('everything', url)).toEqual(['works']); + expect(urlUtils.getParameterValues('test', url)).toEqual([]); + }); + }); + describe('mergeUrlParams', () => { it('adds w', () => { expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag'); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index e4d62b044ca..8b6f7802b15 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -75,3 +75,18 @@ global.MutationObserver = () => ({ disconnect: () => {}, observe: () => {}, }); + +Object.assign(global, { + requestIdleCallback(cb) { + const start = Date.now(); + return setTimeout(() => { + cb({ + didTimeout: false, + timeRemaining: () => Math.max(0, 50 - (Date.now() - start)), + }); + }); + }, + cancelIdleCallback(id) { + clearTimeout(id); + }, +}); diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb index 49e23366355..059ae128d93 100644 --- a/spec/helpers/dashboard_helper_spec.rb +++ b/spec/helpers/dashboard_helper_spec.rb @@ -12,7 +12,7 @@ describe DashboardHelper do it 'has all the expected links by default' do menu_items = [:projects, :groups, :activity, :milestones, :snippets] - expect(helper.dashboard_nav_links).to contain_exactly(*menu_items) + expect(helper.dashboard_nav_links).to include(*menu_items) end it 'does not contain cross project elements when the user cannot read cross project' do diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index f3649495493..a6623bc7941 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -27,7 +27,7 @@ describe UsersHelper do context 'with public profile' do it 'includes all the expected tabs' do - expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets) + expect(tabs).to include(:activity, :groups, :contributed, :projects, :starred, :snippets) end end diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index e6a969bd855..b2fe315f6c6 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -224,7 +224,7 @@ describe('AjaxFormVariableList', () => { describe('maskableRegex', () => { it('takes in the regex provided by the data attribute', () => { - expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$'); expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex); }); }); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index 064113e879a..c8d6f789ed0 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -162,7 +162,7 @@ describe('VariableList', () => { }); it('has a regex provided via a data attribute', () => { - expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$'); + expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$'); }); it('allows values that are 8 characters long', done => { diff --git a/spec/frontend/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js index dfba5a936ee..c6eac4e27b3 100644 --- a/spec/frontend/jobs/components/empty_state_spec.js +++ b/spec/javascripts/jobs/components/empty_state_spec.js @@ -105,7 +105,7 @@ describe('Empty State', () => { }); describe('with playbale action and not scheduled job', () => { - it('renders manual variables form', () => { + beforeEach(() => { vm = mountComponent(Component, { ...props, content, @@ -117,9 +117,15 @@ describe('Empty State', () => { method: 'post', }, }); + }); + it('renders manual variables form', () => { expect(vm.$el.querySelector('.js-manual-vars-form')).not.toBeNull(); }); + + it('does not render the empty state action', () => { + expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull(); + }); }); describe('with playbale action and scheduled job', () => { diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index b53890f8348..d3c1cf831bb 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -24,6 +24,7 @@ describe('Job App ', () => { variablesSettingsUrl: 'settings/ci-cd/variables', terminalPath: 'jobs/123/terminal', pagePath: `${gl.TEST_HOST}jobs/123`, + projectPath: 'user-name/project-name', logState: 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', }; diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index d3e10194d92..36f650d5933 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -307,7 +307,7 @@ describe('Dashboard', () => { }); spyOn(component.$store, 'dispatch').and.stub(); - const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); + const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough(); component.$store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, @@ -319,7 +319,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { expect(component.$store.dispatch).toHaveBeenCalled(); - expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow); + expect(getTimeDiffSpy).toHaveBeenCalled(); done(); }) @@ -327,7 +327,17 @@ describe('Dashboard', () => { }); it('shows a specific time window selected from the url params', done => { - spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']); + const start = 1564439536; + const end = 1564441336; + spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({ + start, + end, + }); + spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => { + if (param === 'start') return [start]; + if (param === 'end') return [end]; + return []; + }); component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index 677455275de..955a39e03a5 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -313,8 +313,8 @@ describe('Monitoring store actions', () => { it('commits prometheus query result', done => { const commit = jasmine.createSpy(); const params = { - start: '1557216349.469', - end: '1557218149.469', + start: '2019-08-06T12:40:02.184Z', + end: '2019-08-06T20:40:02.184Z', }; const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0]; const state = storeState(); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js index 5570d57b8b2..e22e8cdc03d 100644 --- a/spec/javascripts/monitoring/utils_spec.js +++ b/spec/javascripts/monitoring/utils_spec.js @@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants'; import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data'; describe('getTimeDiff', () => { + function secondsBetween({ start, end }) { + return (new Date(end) - new Date(start)) / 1000; + } + + function minutesBetween(timeRange) { + return secondsBetween(timeRange) / 60; + } + + function hoursBetween(timeRange) { + return minutesBetween(timeRange) / 60; + } + it('defaults to an 8 hour (28800s) difference', () => { const params = getTimeDiff(); - expect(params.end - params.start).toEqual(28800); + expect(hoursBetween(params)).toEqual(8); }); it('accepts time window as an argument', () => { - const params = getTimeDiff(timeWindows.thirtyMinutes); + const params = getTimeDiff('thirtyMinutes'); - expect(params.end - params.start).not.toEqual(28800); + expect(minutesBetween(params)).toEqual(30); }); it('returns a value for every defined time window', () => { const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); - nonDefaultWindows.forEach(window => { - const params = getTimeDiff(timeWindows[window]); - const diff = params.end - params.start; + nonDefaultWindows.forEach(timeWindow => { + const params = getTimeDiff(timeWindow); - // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs) - expect(diff).not.toEqual(28800); - expect(typeof diff).toEqual('number'); + // Ensure we're not returning the default + expect(hoursBetween(params)).not.toEqual(8); }); }); }); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index c461c28a37b..e55aa0e965a 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -892,4 +892,31 @@ describe('Actions Notes Store', () => { }); }); }); + + describe('filterDiscussion', () => { + const path = 'some-discussion-path'; + const filter = 0; + + beforeEach(() => { + dispatch.and.returnValue(new Promise(() => {})); + }); + + it('fetches discussions with filter and persistFilter false', () => { + actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false }); + + expect(dispatch.calls.allArgs()).toEqual([ + ['setLoadingState', true], + ['fetchDiscussions', { path, filter, persistFilter: false }], + ]); + }); + + it('fetches discussions with filter and persistFilter true', () => { + actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true }); + + expect(dispatch.calls.allArgs()).toEqual([ + ['setLoadingState', true], + ['fetchDiscussions', { path, filter, persistFilter: true }], + ]); + }); + }); }); diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js index 2fdfff3db03..d15758be5d2 100644 --- a/spec/javascripts/persistent_user_callout_spec.js +++ b/spec/javascripts/persistent_user_callout_spec.js @@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => { return fixture; } + function createDeferredLinkFixture() { + const fixture = document.createElement('div'); + fixture.innerHTML = ` + <div + class="container" + data-dismiss-endpoint="${dismissEndpoint}" + data-feature-id="${featureName}" + data-defer-links="true" + > + <button type="button" class="js-close"></button> + <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a> + <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a> + </div> + `; + + return fixture; + } + describe('dismiss', () => { let button; let mockAxios; @@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => { }); }); + describe('deferred links', () => { + let button; + let deferredLink; + let normalLink; + let mockAxios; + let persistentUserCallout; + let windowSpy; + + beforeEach(() => { + const fixture = createDeferredLinkFixture(); + const container = fixture.querySelector('.container'); + button = fixture.querySelector('.js-close'); + deferredLink = fixture.querySelector('.deferred-link'); + normalLink = fixture.querySelector('.normal-link'); + mockAxios = new MockAdapter(axios); + persistentUserCallout = new PersistentUserCallout(container); + spyOn(persistentUserCallout.container, 'remove'); + windowSpy = spyOn(window, 'open').and.callFake(() => {}); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('defers loading of a link until callout is dismissed', done => { + const { href, target } = deferredLink; + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + deferredLink.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).toHaveBeenCalledWith(href, target); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe( + JSON.stringify({ feature_name: featureName }), + ); + }) + .then(done) + .catch(done.fail); + }); + + it('does not dismiss callout on non-deferred links', done => { + normalLink.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not follow link when notification is closed', done => { + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + button.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('factory', () => { it('returns an instance of PersistentUserCallout with the provided container property', () => { const fixture = createFixture(); diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js index a7dcd532f4f..953a42b9d15 100644 --- a/spec/javascripts/pipelines/pipelines_actions_spec.js +++ b/spec/javascripts/pipelines/pipelines_actions_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; -import eventHub from '~/pipelines/event_hub'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import PipelinesActions from '~/pipelines/components/pipelines_actions.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; import { TEST_HOST } from 'spec/test_constants'; @@ -7,9 +8,15 @@ import { TEST_HOST } from 'spec/test_constants'; describe('Pipelines Actions dropdown', () => { const Component = Vue.extend(PipelinesActions); let vm; + let mock; afterEach(() => { vm.$destroy(); + mock.restore(); + }); + + beforeEach(() => { + mock = new MockAdapter(axios); }); describe('manual actions', () => { @@ -40,6 +47,22 @@ describe('Pipelines Actions dropdown', () => { expect(dropdownItem).toBeDisabled(); }); + + describe('on click', () => { + it('makes a request and toggles the loading state', done => { + mock.onPost(actions.path).reply(200); + + vm.$el.querySelector('.dropdown-menu li button').click(); + + expect(vm.isLoading).toEqual(true); + + setTimeout(() => { + expect(vm.isLoading).toEqual(false); + + done(); + }); + }); + }); }); describe('scheduled jobs', () => { @@ -71,26 +94,27 @@ describe('Pipelines Actions dropdown', () => { .catch(done.fail); }); - it('emits postAction event after confirming', () => { - const emitSpy = jasmine.createSpy('emit'); - eventHub.$on('postAction', emitSpy); + it('makes post request after confirming', done => { + mock.onPost(scheduledJobAction.path).reply(200); spyOn(window, 'confirm').and.callFake(() => true); findDropdownItem(scheduledJobAction).click(); expect(window.confirm).toHaveBeenCalled(); - expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path); + setTimeout(() => { + expect(mock.history.post.length).toBe(1); + done(); + }); }); - it('does not emit postAction event if confirmation is cancelled', () => { - const emitSpy = jasmine.createSpy('emit'); - eventHub.$on('postAction', emitSpy); + it('does not make post request if confirmation is cancelled', () => { + mock.onPost(scheduledJobAction.path).reply(200); spyOn(window, 'confirm').and.callFake(() => false); findDropdownItem(scheduledJobAction).click(); expect(window.confirm).toHaveBeenCalled(); - expect(emitSpy).not.toHaveBeenCalled(); + expect(mock.history.post.length).toBe(0); }); it('displays the remaining time in the dropdown', () => { diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index 1bc0335cfc0..326703eea05 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -105,6 +105,17 @@ describe Banzai::Filter::CommitReferenceFilter do expect(doc.css('a').first[:href]).to eq(url) end + + context "a doc with many (29) strings that could be SHAs" do + let!(:oids) { noteable.commits.collect(&:id) } + + it 'makes only a single request to Gitaly' do + expect(Gitlab::GitalyClient).to receive(:allow_n_plus_1_calls).exactly(0).times + expect(Gitlab::Git::Commit).to receive(:batch_by_oid).once.and_call_original + + reference_filter("A big list of SHAs #{oids.join(", ")}", noteable: noteable) + end + end end end diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 62b688d4d3e..280941ff601 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -173,7 +173,7 @@ describe Gitlab::BitbucketImport::Importer do context 'when importing a pull request throws an exception' do before do allow(pull_request).to receive(:raw).and_return('hello world') - allow(subject.client).to receive(:pull_request_comments).and_raise(HTTParty::Error) + allow(subject.client).to receive(:pull_request_comments).and_raise(Gitlab::HTTP::Error) end it 'logs an error without the backtrace' do diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index d88a2097ba2..775550f2acc 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do - let(:build) { create(:ci_build) } - describe '#unmet?' do + let(:build) { create(:ci_build) } + subject { described_class.new(build).unmet? } context 'build has no deployment' do @@ -18,7 +18,6 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do context 'build has a deployment' do let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) } - let(:cluster) { nil } context 'and a cluster to deploy to' do let(:cluster) { create(:cluster, :group) } @@ -32,12 +31,17 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'and a namespace is already created for this project' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: build.project) } + let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: 'token') } + + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .and_return(double(execute: kubernetes_namespace)) + end it { is_expected.to be_falsey } context 'and the service_account_token is blank' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :without_token, cluster: cluster, project: build.project) } + let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) } it { is_expected.to be_truthy } end @@ -45,34 +49,79 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'and no cluster to deploy to' do + let(:cluster) { nil } + it { is_expected.to be_falsey } end end end describe '#complete!' do - let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) } - let(:service) { double(execute: true) } - let(:cluster) { nil } + let(:build) { create(:ci_build) } + let(:prerequisite) { described_class.new(build) } - subject { described_class.new(build).complete! } + subject { prerequisite.complete! } context 'completion is required' do let(:cluster) { create(:cluster, :group) } + let(:deployment) { create(:deployment, cluster: cluster) } + let(:service) { double(execute: true) } + let(:kubernetes_namespace) { double } + + before do + allow(prerequisite).to receive(:unmet?).and_return(true) + allow(build).to receive(:deployment).and_return(deployment) + end + + context 'kubernetes namespace does not exist' do + let(:namespace_builder) { double(execute: kubernetes_namespace)} - it 'creates a kubernetes namespace' do - expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) - .to receive(:new) - .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace)) - .and_return(service) + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .and_return(double(execute: nil)) + end - expect(service).to receive(:execute).once + it 'creates a namespace using a new record' do + expect(Clusters::BuildKubernetesNamespaceService) + .to receive(:new) + .with(cluster, environment: deployment.environment) + .and_return(namespace_builder) - subject + expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) + .to receive(:new) + .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) + .and_return(service) + + expect(service).to receive(:execute).once + + subject + end + end + + context 'kubernetes namespace exists (but has no service_account_token)' do + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .and_return(double(execute: kubernetes_namespace)) + end + + it 'creates a namespace using the tokenless record' do + expect(Clusters::BuildKubernetesNamespaceService).not_to receive(:new) + + expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService) + .to receive(:new) + .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) + .and_return(service) + + subject + end end end context 'completion is not required' do + before do + allow(prerequisite).to receive(:unmet?).and_return(false) + end + it 'does not create a namespace' do expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new) diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb new file mode 100644 index 00000000000..1d404915617 --- /dev/null +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ContentSecurityPolicy::ConfigLoader do + let(:policy) { ActionDispatch::ContentSecurityPolicy.new } + let(:csp_config) do + { + enabled: true, + report_only: false, + directives: { + base_uri: 'http://example.com', + child_src: "'self' https://child.example.com", + default_src: "'self' https://other.example.com", + script_src: "'self' https://script.exammple.com ", + worker_src: "data: https://worker.example.com", + report_uri: "http://example.com" + } + } + end + + context '.default_settings_hash' do + it 'returns empty defaults' do + settings = described_class.default_settings_hash + + expect(settings['enabled']).to be_falsey + expect(settings['report_only']).to be_falsey + + described_class::DIRECTIVES.each do |directive| + expect(settings['directives'].has_key?(directive)).to be_truthy + expect(settings['directives'][directive]).to be_nil + end + end + end + + context '#load' do + subject { described_class.new(csp_config[:directives]) } + + def expected_config(directive) + csp_config[:directives][directive].split(' ').map(&:strip) + end + + it 'sets the policy properly' do + subject.load(policy) + + expect(policy.directives['base-uri']).to eq([csp_config[:directives][:base_uri]]) + expect(policy.directives['default-src']).to eq(expected_config(:default_src)) + expect(policy.directives['child-src']).to eq(expected_config(:child_src)) + expect(policy.directives['worker-src']).to eq(expected_config(:worker_src)) + expect(policy.directives['report-uri']).to eq(expected_config(:report_uri)) + end + + it 'ignores malformed policy statements' do + csp_config[:directives][:base_uri] = 123 + + subject.load(policy) + + expect(policy.directives['base-uri']).to be_nil + end + end +end diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 2759412add8..eced96a4c77 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -14,6 +14,16 @@ describe Gitlab::CurrentSettings do end end + describe '.expire_current_application_settings', :use_clean_rails_memory_store_caching, :request_store do + include_context 'with settings in cache' + + it 'expires the cache' do + described_class.expire_current_application_settings + + expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).not_to eq(0) + end + end + describe '#current_application_settings', :use_clean_rails_memory_store_caching do it 'allows keys to be called directly' do db_settings = create(:application_setting, diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 4676db6b8d8..a410e4eab45 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -62,6 +62,14 @@ describe Gitlab::Highlight do expect(lines[2].text).to eq(' """') end + context 'since param is present' do + it 'highlights with the LC starting from "since" param' do + lines = described_class.highlight(file_name, content, since: 2).lines + + expect(lines[0]).to include('LC2') + end + end + context 'diff highlighting' do let(:file_name) { 'test.diff' } let(:content) { "+aaa\n+bbb\n- ccc\n ddd\n"} diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb index 158f77cab2c..d3f9be845dd 100644 --- a/spec/lib/gitlab/http_spec.rb +++ b/spec/lib/gitlab/http_spec.rb @@ -23,14 +23,14 @@ describe Gitlab::HTTP do end end - describe 'allow_local_requests_from_hooks_and_services is' do + describe 'allow_local_requests_from_web_hooks_and_services is' do before do WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success') end context 'disabled' do before do - allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(false) + allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false) end it 'deny requests to localhost' do @@ -52,7 +52,7 @@ describe Gitlab::HTTP do context 'enabled' do before do - allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(true) + allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true) end it 'allow requests to localhost' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 929b6222900..ada8c649ff6 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -469,3 +469,17 @@ incident_management_setting: merge_trains: - project - merge_request +boards: +- group +- lists +- destroyable_lists +- milestone +- board_labels +- board_assignee +- assignee +- labels +lists: +- user +- milestone +- board +- label diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 9e54ca28e58..6d70b147666 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -7147,5 +7147,65 @@ "link_url": "http://www.example.com", "image_url": "http://www.example.com" } + ], + "boards": [ + { + "id": 29, + "project_id": 49, + "created_at": "2019-06-06T14:01:06.204Z", + "updated_at": "2019-06-06T14:22:37.045Z", + "name": "TestBoardABC", + "milestone_id": null, + "group_id": null, + "weight": null, + "lists": [ + { + "id": 59, + "board_id": 29, + "label_id": null, + "list_type": "backlog", + "position": null, + "created_at": "2019-06-06T14:01:06.214Z", + "updated_at": "2019-06-06T14:01:06.214Z", + "user_id": null, + "milestone_id": null + }, + { + "id": 61, + "board_id": 29, + "label_id": 20, + "list_type": "label", + "position": 0, + "created_at": "2019-06-06T14:01:43.197Z", + "updated_at": "2019-06-06T14:01:43.197Z", + "user_id": null, + "milestone_id": null, + "label": { + "id": 20, + "title": "testlabel", + "color": "#0033CC", + "project_id": 49, + "created_at": "2019-06-06T14:01:19.698Z", + "updated_at": "2019-06-06T14:01:19.698Z", + "template": false, + "description": null, + "group_id": null, + "type": "ProjectLabel", + "priorities": [] + } + }, + { + "id": 60, + "board_id": 29, + "label_id": null, + "list_type": "closed", + "position": null, + "created_at": "2019-06-06T14:01:06.221Z", + "updated_at": "2019-06-06T14:01:06.221Z", + "user_id": null, + "milestone_id": null + } + ] + } ] } diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index b9f6595762b..baec24590b4 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -160,13 +160,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end it 'has project labels' do - expect(ProjectLabel.count).to eq(2) + expect(ProjectLabel.count).to eq(3) end it 'has no group labels' do expect(GroupLabel.count).to eq(0) end + it 'has issue boards' do + expect(Project.find_by_path('project').boards.count).to eq(1) + end + + it 'has lists associated with the issue board' do + expect(Project.find_by_path('project').boards.find_by_name('TestBoardABC').lists.count).to eq(3) + end + it 'has a project feature' do expect(@project.project_feature).not_to be_nil end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 1ff2eb9210f..fefbed93316 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -272,6 +272,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(saved_project_json).not_to include("runners_token" => 'token') end end + + it 'has a board and a list' do + expect(saved_project_json['boards'].first['lists']).not_to be_empty + end end end @@ -327,6 +331,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do create(:project_badge, project: project) create(:project_badge, project: project) + board = create(:board, project: project, name: 'TestBoard') + create(:list, board: board, position: 0, label: project_label) + project end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 28b187c3676..f0545176a90 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -235,6 +235,12 @@ MergeRequest::Metrics: - latest_build_started_at - latest_build_finished_at - first_deployed_to_production_at +- first_comment_at +- first_commit_at +- last_commit_at +- diff_size +- modified_paths_size +- commits_count Ci::Pipeline: - id - project_id @@ -687,3 +693,22 @@ ProjectMetricsSetting: - external_dashboard_url - created_at - updated_at +Board: +- id +- project_id +- created_at +- updated_at +- group_id +- milestone_id +- weight +- name +List: +- id +- board_id +- label_id +- list_type +- position +- created_at +- updated_at +- milestone_id +- user_id diff --git a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb new file mode 100644 index 00000000000..1fda547f35c --- /dev/null +++ b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Kubernetes::DefaultNamespace do + let(:generator) { described_class.new(cluster, project: environment.project) } + + describe '#from_environment_name' do + let(:cluster) { create(:cluster) } + let(:environment) { create(:environment) } + + subject { generator.from_environment_name(environment.name) } + + it 'generates a slug and passes it to #from_environment_slug' do + expect(Gitlab::Slug::Environment).to receive(:new) + .with(environment.name) + .and_return(double(generate: environment.slug)) + + expect(generator).to receive(:from_environment_slug) + .with(environment.slug) + .and_return(:mock_namespace) + + expect(subject).to eq :mock_namespace + end + end + + describe '#from_environment_slug' do + let(:platform) { create(:cluster_platform_kubernetes, namespace: platform_namespace) } + let(:cluster) { create(:cluster, platform_kubernetes: platform) } + let(:project) { create(:project, path: "Path-With-Capitals") } + let(:environment) { create(:environment, project: project) } + + subject { generator.from_environment_slug(environment.slug) } + + context 'namespace per environment is enabled' do + context 'platform namespace is specified' do + let(:platform_namespace) { 'platform-namespace' } + + it { is_expected.to eq "#{platform_namespace}-#{environment.slug}" } + + context 'cluster is unmanaged' do + let(:cluster) { create(:cluster, :not_managed, platform_kubernetes: platform) } + + it { is_expected.to eq platform_namespace } + end + end + + context 'platform namespace is blank' do + let(:platform_namespace) { nil } + let(:mock_namespace) { 'mock-namespace' } + + it 'constructs a namespace from the project and environment' do + expect(Gitlab::NamespaceSanitizer).to receive(:sanitize) + .with("#{project.path}-#{project.id}-#{environment.slug}".downcase) + .and_return(mock_namespace) + + expect(subject).to eq mock_namespace + end + end + end + + context 'namespace per environment is disabled' do + let(:cluster) { create(:cluster, :namespace_per_environment_disabled, platform_kubernetes: platform) } + + context 'platform namespace is specified' do + let(:platform_namespace) { 'platform-namespace' } + + it { is_expected.to eq platform_namespace } + end + + context 'platform namespace is blank' do + let(:platform_namespace) { nil } + let(:mock_namespace) { 'mock-namespace' } + + it 'constructs a namespace from the project and environment' do + expect(Gitlab::NamespaceSanitizer).to receive(:sanitize) + .with("#{project.path}-#{project.id}".downcase) + .and_return(mock_namespace) + + expect(subject).to eq mock_namespace + end + end + end + end +end diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb index 97ebb5f1554..f49d4e23e39 100644 --- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb +++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb @@ -58,7 +58,7 @@ describe Gitlab::Kubernetes::KubeClient do context 'when local requests are allowed' do before do - stub_application_setting(allow_local_requests_from_hooks_and_services: true) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) end it 'allows local addresses' do diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb new file mode 100644 index 00000000000..420b246b3f5 --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::Defaults do + it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) } + it { is_expected.to be_const_defined(:DEFAULT_PANEL_WEIGHT) } +end diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb index e57c7326320..ce1bb49f5c9 100644 --- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -5,10 +5,9 @@ require 'spec_helper' describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do include MetricsDashboardHelpers - set(:project) { build(:project) } + set(:project) { create(:project) } set(:user) { create(:user) } set(:environment) { create(:environment, project: project) } - let(:system_dashboard_path) { ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH} before do project.add_maintainer(user) @@ -52,9 +51,80 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi end context 'when the dashboard is expected to be embedded' do - let(:service_call) { described_class.find(project, user, environment, dashboard_path: nil, embedded: true) } + let(:service_call) { described_class.find(project, user, environment, **params) } + let(:params) { { embedded: true } } it_behaves_like 'valid embedded dashboard service response' + + context 'when params are incomplete' do + let(:params) { { embedded: true, dashboard_path: system_dashboard_path } } + + it_behaves_like 'valid embedded dashboard service response' + end + + context 'when the panel is specified' do + context 'as a custom metric' do + let(:params) do + { embedded: true, + dashboard_path: system_dashboard_path, + group: business_metric_title, + title: 'title', + y_label: 'y_label' } + end + + it_behaves_like 'misconfigured dashboard service response', :not_found + + context 'when the metric exists' do + before do + create(:prometheus_metric, project: project) + end + + it_behaves_like 'valid embedded dashboard service response' + end + end + + context 'as a project-defined panel' do + let(:dashboard_path) { '.gitlab/dashboard/test.yml' } + let(:params) do + { embedded: true, + dashboard_path: dashboard_path, + group: 'Group A', + title: 'Super Chart A1', + y_label: 'y_label' } + end + + it_behaves_like 'misconfigured dashboard service response', :not_found + + context 'when the metric exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid embedded dashboard service response' + end + end + end + end + end + + describe '.find_raw' do + let(:dashboard) { YAML.load_file(Rails.root.join('config', 'prometheus', 'common_metrics.yml')) } + let(:params) { {} } + + subject { described_class.find_raw(project, **params) } + + it { is_expected.to eq dashboard } + + context 'when the system dashboard is specified' do + let(:params) { { dashboard_path: system_dashboard_path } } + + it { is_expected.to eq dashboard } + end + + context 'when an existing project dashboard is specified' do + let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) } + let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } } + let(:project) { project_with_dashboard(params[:dashboard_path]) } + + it { is_expected.to eq dashboard } end end diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb new file mode 100644 index 00000000000..095d0a2df78 --- /dev/null +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Metrics::Dashboard::ServiceSelector do + include MetricsDashboardHelpers + + describe '#call' do + let(:arguments) { {} } + + subject { described_class.call(arguments) } + + it { is_expected.to be Metrics::Dashboard::SystemDashboardService } + + context 'when just the dashboard path is provided' do + let(:arguments) { { dashboard_path: '.gitlab/dashboards/test.yml' } } + + it { is_expected.to be Metrics::Dashboard::ProjectDashboardService } + + context 'when the path is for the system dashboard' do + let(:arguments) { { dashboard_path: system_dashboard_path } } + + it { is_expected.to be Metrics::Dashboard::SystemDashboardService } + end + end + + context 'when the embedded flag is provided' do + let(:arguments) { { embedded: true } } + + it { is_expected.to be Metrics::Dashboard::DefaultEmbedService } + + context 'when an incomplete set of dashboard identifiers are provided' do + let(:arguments) { { embedded: true, dashboard_path: '.gitlab/dashboards/test.yml' } } + + it { is_expected.to be Metrics::Dashboard::DefaultEmbedService } + end + + context 'when all the chart identifiers are provided' do + let(:arguments) do + { + embedded: true, + dashboard_path: '.gitlab/dashboards/test.yml', + group: 'Important Metrics', + title: 'Total Requests', + y_label: 'req/sec' + } + end + + it { is_expected.to be Metrics::Dashboard::DynamicEmbedService } + end + + context 'when all chart params expect dashboard_path are provided' do + let(:arguments) do + { + embedded: true, + group: 'Important Metrics', + title: 'Total Requests', + y_label: 'req/sec' + } + end + + it { is_expected.to be Metrics::Dashboard::DynamicEmbedService } + end + + context 'with a system dashboard and "custom" group' do + let(:arguments) do + { + embedded: true, + dashboard_path: system_dashboard_path, + group: business_metric_title, + title: 'Total Requests', + y_label: 'req/sec' + } + end + + it { is_expected.to be Metrics::Dashboard::CustomMetricEmbedService } + end + end + end +end diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb index 7f2b523f5b7..43f6d13f7ba 100644 --- a/spec/lib/gitlab/octokit/middleware_spec.rb +++ b/spec/lib/gitlab/octokit/middleware_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Octokit::Middleware do context 'when localhost requests are not allowed' do before do - stub_application_setting(allow_local_requests_from_hooks_and_services: false) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: false) end it_behaves_like 'Local URL' @@ -38,7 +38,7 @@ describe Gitlab::Octokit::Middleware do context 'when localhost requests are allowed' do before do - stub_application_setting(allow_local_requests_from_hooks_and_services: true) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) end it_behaves_like 'Public URL' @@ -50,7 +50,7 @@ describe Gitlab::Octokit::Middleware do context 'when local network requests are not allowed' do before do - stub_application_setting(allow_local_requests_from_hooks_and_services: false) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: false) end it_behaves_like 'Local URL' @@ -58,7 +58,7 @@ describe Gitlab::Octokit::Middleware do context 'when local network requests are allowed' do before do - stub_application_setting(allow_local_requests_from_hooks_and_services: true) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) end it_behaves_like 'Public URL' diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb index 6dc99ef26ec..3f9b245a3fb 100644 --- a/spec/lib/gitlab/prometheus/query_variables_spec.rb +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Prometheus::QueryVariables do context 'with deployment platform' do context 'with project cluster' do - let(:kube_namespace) { environment.deployment_platform.cluster.kubernetes_namespace_for(project) } + let(:kube_namespace) { environment.deployment_namespace } before do create(:cluster, :project, :provided_by_user, projects: [project]) @@ -38,8 +38,8 @@ describe Gitlab::Prometheus::QueryVariables do let(:project2) { create(:project) } let(:kube_namespace) { k8s_ns.namespace } - let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project) } - let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2) } + let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project, environment: environment) } + let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2, environment: environment) } before do group.projects << project diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index f15ae83a02c..0a4e8dbced5 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::PrometheusClient do include PrometheusHelpers - subject { described_class.new(RestClient::Resource.new('https://prometheus.example.com')) } + subject { described_class.new('https://prometheus.example.com') } describe '#ping' do it 'issues a "query" request to the API endpoint' do @@ -79,8 +79,16 @@ describe Gitlab::PrometheusClient do expect(req_stub).to have_been_requested end - it 'raises a Gitlab::PrometheusClient::Error error when a RestClient::Exception is rescued' do - req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception) + it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError is rescued' do + req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError) + + expect { subject } + .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error") + expect(req_stub).to have_been_requested + end + + it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError with a code is rescued' do + req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError.new(code: 400)) expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error") @@ -89,13 +97,13 @@ describe Gitlab::PrometheusClient do end context 'ping' do - subject { described_class.new(RestClient::Resource.new(prometheus_url)).ping } + subject { described_class.new(prometheus_url).ping } it_behaves_like 'exceptions are raised' end context 'proxy' do - subject { described_class.new(RestClient::Resource.new(prometheus_url)).proxy('query', { query: '1' }) } + subject { described_class.new(prometheus_url).proxy('query', { query: '1' }) } it_behaves_like 'exceptions are raised' end @@ -310,15 +318,32 @@ describe Gitlab::PrometheusClient do end end - context 'when RestClient::Exception is raised' do + context 'when Gitlab::HTTP::ResponseError is raised' do before do - stub_prometheus_request_with_exception(query_url, RestClient::Exception) + stub_prometheus_request_with_exception(query_url, response_error) + end + + context "without response code" do + let(:response_error) { Gitlab::HTTP::ResponseError } + it 'raises PrometheusClient::Error' do + expect { subject.proxy('query', { query: prometheus_query }) }.to( + raise_error(Gitlab::PrometheusClient::Error, 'Network connection error') + ) + end end - it 'raises PrometheusClient::Error' do - expect { subject.proxy('query', { query: prometheus_query }) }.to( - raise_error(Gitlab::PrometheusClient::Error, 'Network connection error') - ) + context "with response code" do + let(:response_error) do + response = Net::HTTPResponse.new(1.1, 400, '{}sumpthin') + allow(response).to receive(:body) { '{}' } + Gitlab::HTTP::ResponseError.new(response) + end + + it 'raises Gitlab::PrometheusClient::QueryError' do + expect { subject.proxy('query', { query: prometheus_query }) }.to( + raise_error(Gitlab::PrometheusClient::QueryError, 'Bad data received') + ) + end end end end diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index cb14204b99a..ca2b17b44e0 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -63,7 +63,7 @@ describe Sentry::Client do shared_examples 'maps exceptions' do exceptions = { - HTTParty::Error => 'Error when connecting to Sentry', + Gitlab::HTTP::Error => 'Error when connecting to Sentry', Net::OpenTimeout => 'Connection to Sentry timed out', SocketError => 'Received SocketError when trying to connect to Sentry', OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data', diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 0387073cffb..b7e005e3883 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -208,6 +208,22 @@ describe Ci::Build do end end + describe '.without_needs' do + let!(:build) { create(:ci_build) } + + subject { described_class.without_needs } + + context 'when no build_need is created' do + it { is_expected.to contain_exactly(build) } + end + + context 'when a build_need is created' do + let!(:need_a) { create(:ci_build_need, build: build) } + + it { is_expected.to be_empty } + end + end + describe '#enqueue' do let(:build) { create(:ci_build, :created) } diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index e956a2355db..93050e80b07 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -13,7 +13,7 @@ describe Clusters::Applications::CertManager do describe '#can_uninstall?' do subject { cert_manager.can_uninstall? } - it { is_expected.to be_falsey } + it { is_expected.to be_truthy } end describe '#install_command' do @@ -80,6 +80,44 @@ describe Clusters::Applications::CertManager do end end + describe '#uninstall_command' do + subject { cert_manager.uninstall_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) } + + it 'is initialized with cert_manager arguments' do + expect(subject.name).to eq('certmanager') + expect(subject).to be_rbac + expect(subject.files).to eq(cert_manager.files) + end + + it 'specifies a post delete command to remove custom resource definitions' do + expect(subject.postdelete).to eq([ + "kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found", + 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found' + ]) + end + + context 'secret key name is not found' do + before do + allow(File).to receive(:read).and_call_original + expect(File).to receive(:read) + .with(Rails.root.join('vendor', 'cert_manager', 'cluster_issuer.yaml')) + .and_return('key: value') + end + + it 'does not try and delete the secret' do + expect(subject.postdelete).to eq([ + 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found', + 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found' + ]) + end + end + end + describe '#files' do let(:application) { cert_manager } let(:values) { subject[:'values.yaml'] } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index d9f31c46f59..eb6ccba5584 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -86,16 +86,15 @@ describe Clusters::Applications::Prometheus do project: cluster.cluster_project.project) end - it 'creates proxy prometheus rest client' do - expect(subject.prometheus_client).to be_instance_of(RestClient::Resource) + it 'creates proxy prometheus_client' do + expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient) end - it 'creates proper url' do - expect(subject.prometheus_client.url).to eq("#{kubernetes_url}/api/v1/namespaces/gitlab-managed-apps/services/prometheus-prometheus-server:80/proxy") - end - - it 'copies options and headers from kube client to proxy client' do - expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) + it 'copies proxy_url, options and headers from kube client to prometheus_client' do + expect(Gitlab::PrometheusClient) + .to(receive(:new)) + .with(a_valid_url, kube_client.rest_client.options.merge(headers: kube_client.headers)) + subject.prometheus_client end context 'when cluster is not reachable' do diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 96212d0c864..9afbe6328ca 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -38,11 +38,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do it { is_expected.to respond_to :project } - it do - expect(subject.knative_services_finder(subject.project)) - .to be_instance_of(Clusters::KnativeServicesFinder) - end - describe '.enabled' do subject { described_class.enabled } @@ -534,60 +529,39 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end - describe '#find_or_initialize_kubernetes_namespace_for_project' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.projects.first } - - subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project) } - - context 'kubernetes namespace exists' do - context 'with no service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) } - - it { is_expected.to eq kubernetes_namespace } - end + describe '#kubernetes_namespace_for' do + let(:cluster) { create(:cluster, :group) } + let(:environment) { create(:environment) } - context 'with a service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) } + subject { cluster.kubernetes_namespace_for(environment) } - it { is_expected.to eq kubernetes_namespace } - end - end - - context 'kubernetes namespace does not exist' do - it 'initializes a new namespace and sets default values' do - expect(subject).to be_new_record - expect(subject.project).to eq project - expect(subject.cluster).to eq cluster - expect(subject.namespace).to be_present - expect(subject.service_account_name).to be_present - end + before do + expect(Clusters::KubernetesNamespaceFinder).to receive(:new) + .with(cluster, project: environment.project, environment_slug: environment.slug) + .and_return(double(execute: persisted_namespace)) end - context 'a custom scope is provided' do - let(:scope) { cluster.kubernetes_namespaces.has_service_account_token } - - subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project, scope: scope) } - - context 'kubernetes namespace exists' do - context 'with no service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) } + context 'a persisted namespace exists' do + let(:persisted_namespace) { create(:cluster_kubernetes_namespace) } - it 'initializes a new namespace and sets default values' do - expect(subject).to be_new_record - expect(subject.project).to eq project - expect(subject.cluster).to eq cluster - expect(subject.namespace).to be_present - expect(subject.service_account_name).to be_present - end - end + it { is_expected.to eq persisted_namespace.namespace } + end - context 'with a service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) } + context 'no persisted namespace exists' do + let(:persisted_namespace) { nil } + let(:namespace_generator) { double } + let(:default_namespace) { 'a-default-namespace' } - it { is_expected.to eq kubernetes_namespace } - end + before do + expect(Gitlab::Kubernetes::DefaultNamespace).to receive(:new) + .with(cluster, project: environment.project) + .and_return(namespace_generator) + expect(namespace_generator).to receive(:from_environment_slug) + .with(environment.slug) + .and_return(default_namespace) end + + it { is_expected.to eq default_namespace } end end diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index b5cba80b806..d4e3a0ac84d 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -24,70 +24,60 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do end end - describe 'namespace uniqueness validation' do - let(:cluster_project) { create(:cluster_project) } - let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') } + describe '.with_environment_slug' do + let(:cluster) { create(:cluster, :group) } + let(:environment) { create(:environment, slug: slug) } - subject { kubernetes_namespace } + let(:slug) { 'production' } - context 'when cluster is using the namespace' do - before do - create(:cluster_kubernetes_namespace, - cluster: kubernetes_namespace.cluster, - namespace: 'my-namespace') - end + subject { described_class.with_environment_slug(slug) } - it { is_expected.not_to be_valid } - end + context 'there is no associated environment' do + let!(:namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: environment.project) } - context 'when cluster is not using the namespace' do - it { is_expected.to be_valid } + it { is_expected.to be_empty } end - end - describe '#set_defaults' do - let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) } - let(:cluster) { kubernetes_namespace.cluster } - let(:platform) { kubernetes_namespace.platform_kubernetes } - - subject { kubernetes_namespace.set_defaults } - - describe '#namespace' do - before do - platform.update_column(:namespace, namespace) + context 'there is an assicated environment' do + let!(:namespace) do + create( + :cluster_kubernetes_namespace, + cluster: cluster, + project: environment.project, + environment: environment + ) end - context 'when platform has a namespace assigned' do - let(:namespace) { 'platform-namespace' } - - it 'copies the namespace' do - subject - - expect(kubernetes_namespace.namespace).to eq('platform-namespace') - end + context 'with a matching slug' do + it { is_expected.to eq [namespace] } end - context 'when platform does not have namespace assigned' do - let(:project) { kubernetes_namespace.project } - let(:namespace) { nil } - let(:project_slug) { "#{project.path}-#{project.id}" } - - it 'fallbacks to project namespace' do - subject + context 'without a matching slug' do + let(:environment) { create(:environment, slug: 'staging') } - expect(kubernetes_namespace.namespace).to eq(project_slug) - end + it { is_expected.to be_empty } end end + end - describe '#service_account_name' do - let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" } + describe 'namespace uniqueness validation' do + let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') } - it 'sets a service account name based on namespace' do - subject + subject { kubernetes_namespace } - expect(kubernetes_namespace.service_account_name).to eq(service_account_name) + context 'when cluster is using the namespace' do + before do + create(:cluster_kubernetes_namespace, + cluster: kubernetes_namespace.cluster, + environment: kubernetes_namespace.environment, + namespace: 'my-namespace') end + + it { is_expected.not_to be_valid } + end + + context 'when cluster is not using the namespace' do + it { is_expected.to be_valid } end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 471769e4aab..0c4cf291d20 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -106,7 +106,7 @@ describe Clusters::Platforms::Kubernetes do before do allow(ApplicationSetting) .to receive(:current) - .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true)) + .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: true)) end it { expect(kubernetes.save).to be_truthy } @@ -205,192 +205,77 @@ describe Clusters::Platforms::Kubernetes do it { is_expected.to be_truthy } end - describe '#kubernetes_namespace_for' do - let(:cluster) { create(:cluster, :project) } - let(:project) { cluster.project } - - let(:platform) do - create(:cluster_platform_kubernetes, - cluster: cluster, - namespace: namespace) - end - - subject { platform.kubernetes_namespace_for(project) } - - context 'with a namespace assigned' do - let(:namespace) { 'namespace-123' } - - it { is_expected.to eq(namespace) } - - context 'kubernetes namespace is present but has no service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } - - it { is_expected.to eq(namespace) } - end - end - - context 'with no namespace assigned' do - let(:namespace) { nil } - - context 'when kubernetes namespace is present' do - let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) } - - before do - kubernetes_namespace - end - - it { is_expected.to eq(kubernetes_namespace.namespace) } - - context 'kubernetes namespace has no service account token' do - before do - kubernetes_namespace.update!(namespace: 'old-namespace', service_account_token: nil) - end + describe '#predefined_variables' do + let(:project) { create(:project) } + let(:cluster) { create(:cluster, :group, platform_kubernetes: platform) } + let(:platform) { create(:cluster_platform_kubernetes) } + let(:persisted_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) } - it { is_expected.to eq("#{project.path}-#{project.id}") } - end - end + let(:environment_name) { 'env/production' } + let(:environment_slug) { Gitlab::Slug::Environment.new(environment_name).generate } - context 'when kubernetes namespace is not present' do - it { is_expected.to eq("#{project.path}-#{project.id}") } - end - end - end + subject { platform.predefined_variables(project: project, environment_name: environment_name) } - describe '#predefined_variables' do - let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } - let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) } - let(:api_url) { 'https://kube.domain.com' } - let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) } - - subject { kubernetes.predefined_variables(project: cluster.project) } - - shared_examples 'setting variables' do - it 'sets the variables' do - expect(subject).to include( - { key: 'KUBE_URL', value: api_url, public: true }, - { key: 'KUBE_CA_PEM', value: ca_pem, public: true }, - { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } - ) - end + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .with(cluster, project: project, environment_slug: environment_slug) + .and_return(double(execute: persisted_namespace)) end - context 'kubernetes namespace is created with no service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } + it { is_expected.to include(key: 'KUBE_URL', value: platform.api_url, public: true) } - it_behaves_like 'setting variables' + context 'platform has a CA certificate' do + let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) } + let(:platform) { create(:cluster_platform_kubernetes, ca_cert: ca_pem) } - it 'does not set KUBE_TOKEN' do - expect(subject).not_to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end + it { is_expected.to include(key: 'KUBE_CA_PEM', value: ca_pem, public: true) } + it { is_expected.to include(key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true) } end - context 'kubernetes namespace is created with service account token' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) } - - it_behaves_like 'setting variables' + context 'kubernetes namespace exists' do + let(:variable) { Hash(key: :fake_key, value: 'fake_value') } + let(:namespace_variables) { Gitlab::Ci::Variables::Collection.new([variable]) } - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } - ) + before do + expect(persisted_namespace).to receive(:predefined_variables).and_return(namespace_variables) end - context 'the cluster has been set to unmanaged after the namespace was created' do - before do - cluster.update!(managed: false) - end - - it_behaves_like 'setting variables' - - it 'sets KUBE_TOKEN from the platform' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end - - context 'the platform has a custom namespace set' do - before do - kubernetes.update!(namespace: 'custom-namespace') - end - - it 'sets KUBE_NAMESPACE from the platform' do - expect(subject).to include( - { key: 'KUBE_NAMESPACE', value: kubernetes.namespace, public: true, masked: false } - ) - end - end - - context 'there is no namespace specified on the platform' do - let(:project) { cluster.project } - - before do - kubernetes.update!(namespace: nil) - end - - it 'sets KUBE_NAMESPACE to a default for the project' do - expect(subject).to include( - { key: 'KUBE_NAMESPACE', value: "#{project.path}-#{project.id}", public: true, masked: false } - ) - end - end - end + it { is_expected.to include(variable) } end - context 'group level cluster' do - let!(:cluster) { create(:cluster, :group, platform_kubernetes: kubernetes) } - - let(:project) { create(:project, group: cluster.group) } - - subject { kubernetes.predefined_variables(project: project) } - - context 'no kubernetes namespace for the project' do - it_behaves_like 'setting variables' - - it 'does not return KUBE_TOKEN' do - expect(subject).not_to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false } - ) - end - - context 'the cluster is not managed' do - let!(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: kubernetes) } + context 'kubernetes namespace does not exist' do + let(:persisted_namespace) { nil } + let(:namespace) { 'kubernetes-namespace' } + let(:kubeconfig) { 'kubeconfig' } - it_behaves_like 'setting variables' - - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } - ) - end - end + before do + allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new) + .with(cluster, project: project).and_return(double(from_environment_name: namespace)) + allow(platform).to receive(:kubeconfig).with(namespace).and_return(kubeconfig) end - context 'kubernetes namespace exists for the project' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: project) } + it { is_expected.not_to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) } + it { is_expected.not_to include(key: 'KUBE_NAMESPACE', value: namespace) } + it { is_expected.not_to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) } - it_behaves_like 'setting variables' + context 'cluster is unmanaged' do + let(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: platform) } - it 'sets KUBE_TOKEN' do - expect(subject).to include( - { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } - ) - end + it { is_expected.to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) } + it { is_expected.to include(key: 'KUBE_NAMESPACE', value: namespace) } + it { is_expected.to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) } end end - context 'with a domain' do - let!(:cluster) do - create(:cluster, :provided_by_gcp, :with_domain, - platform_kubernetes: kubernetes) - end + context 'cluster variables' do + let(:variable) { Hash(key: :fake_key, value: 'fake_value') } + let(:cluster_variables) { Gitlab::Ci::Variables::Collection.new([variable]) } - it 'sets KUBE_INGRESS_BASE_DOMAIN' do - expect(subject).to include( - { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true } - ) + before do + expect(cluster).to receive(:predefined_variables).and_return(cluster_variables) end + + it { is_expected.to include(variable) } end end @@ -410,7 +295,7 @@ describe Clusters::Platforms::Kubernetes do end context 'with valid pods' do - let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) } + let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(environment), project_slug: project.full_path_slug) } let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } let(:pods) { [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] } diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index eeacdadab9c..da46effe411 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -7,6 +7,7 @@ describe CacheableAttributes do Class.new do include ActiveModel::Model extend ActiveModel::Callbacks + include ActiveModel::AttributeMethods define_model_callbacks :commit include CacheableAttributes @@ -34,44 +35,60 @@ describe CacheableAttributes do end end + before do + stub_const("MinimalTestClass", minimal_test_class) + end + shared_context 'with defaults' do before do - minimal_test_class.define_singleton_method(:defaults) do + MinimalTestClass.define_singleton_method(:defaults) do { foo: 'a', bar: 'b', baz: 'c' } end end end + describe '.expire', :use_clean_rails_memory_store_caching, :request_store do + it 'wipes the cache' do + obj = MinimalTestClass.new + obj.cache! + expect(MinimalTestClass.cached).not_to eq(nil) + + MinimalTestClass.expire + + expect(MinimalTestClass.cached).to eq(nil) + end + end + describe '.current_without_cache' do it 'defaults to last' do - expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.last) + expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.last) end it 'can be overridden' do - minimal_test_class.define_singleton_method(:current_without_cache) do + MinimalTestClass.define_singleton_method(:current_without_cache) do first end - expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.first) + expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.first) end end describe '.cache_key' do it 'excludes cache attributes' do - expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}") + expect(MinimalTestClass.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}") end end describe '.defaults' do it 'defaults to {}' do - expect(minimal_test_class.defaults).to eq({}) + expect(MinimalTestClass.defaults).to eq({}) end context 'with defaults defined' do include_context 'with defaults' it 'can be overridden' do - expect(minimal_test_class.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' }) + expect(MinimalTestClass.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' }) end end end @@ -81,13 +98,13 @@ describe CacheableAttributes do context 'without any attributes given' do it 'intializes a new object with the defaults' do - expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults.stringify_keys) + expect(MinimalTestClass.build_from_defaults.attributes).to eq(MinimalTestClass.defaults.stringify_keys) end end context 'with attributes given' do it 'intializes a new object with the given attributes merged into the defaults' do - expect(minimal_test_class.build_from_defaults(foo: 'd').attributes['foo']).to eq('d') + expect(MinimalTestClass.build_from_defaults(foo: 'd').attributes['foo']).to eq('d') end end @@ -108,8 +125,8 @@ describe CacheableAttributes do describe '.current', :use_clean_rails_memory_store_caching do context 'redis unavailable' do before do - allow(minimal_test_class).to receive(:last).and_return(:last) - expect(Rails.cache).to receive(:read).with(minimal_test_class.cache_key).and_raise(Redis::BaseError) + allow(MinimalTestClass).to receive(:last).and_return(:last) + expect(Rails.cache).to receive(:read).with(MinimalTestClass.cache_key).and_raise(Redis::BaseError) end context 'in production environment' do @@ -120,7 +137,7 @@ describe CacheableAttributes do it 'returns an uncached record and logs a warning' do expect(Rails.logger).to receive(:warn).with("Cached record for TestClass couldn't be loaded, falling back to uncached record: Redis::BaseError") - expect(minimal_test_class.current).to eq(:last) + expect(MinimalTestClass.current).to eq(:last) end end @@ -132,7 +149,7 @@ describe CacheableAttributes do it 'returns an uncached record and logs a warning' do expect(Rails.logger).not_to receive(:warn) - expect { minimal_test_class.current }.to raise_error(Redis::BaseError) + expect { MinimalTestClass.current }.to raise_error(Redis::BaseError) end end end @@ -202,7 +219,7 @@ describe CacheableAttributes do describe '.cached', :use_clean_rails_memory_store_caching do context 'when cache is cold' do it 'returns nil' do - expect(minimal_test_class.cached).to be_nil + expect(MinimalTestClass.cached).to be_nil end end diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb index 25a2d290f76..3d26ba95192 100644 --- a/spec/models/concerns/prometheus_adapter_spec.rb +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -40,13 +40,13 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do describe 'matched_metrics' do let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricQuery } - let(:prometheus_client_wrapper) { double(:prometheus_client_wrapper, label_values: nil) } + let(:prometheus_client) { double(:prometheus_client, label_values: nil) } context 'with valid data' do subject { service.query(:matched_metrics) } before do - allow(service).to receive(:prometheus_client_wrapper).and_return(prometheus_client_wrapper) + allow(service).to receive(:prometheus_client).and_return(prometheus_client) synchronous_reactive_cache(service) end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index d2e0bed721e..521c4704c87 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -575,6 +575,34 @@ describe Environment, :use_clean_rails_memory_store_caching do end end + describe '#deployment_namespace' do + let(:environment) { create(:environment) } + + subject { environment.deployment_namespace } + + before do + allow(environment).to receive(:deployment_platform).and_return(deployment_platform) + end + + context 'no deployment platform available' do + let(:deployment_platform) { nil } + + it { is_expected.to be_nil } + end + + context 'deployment platform is available' do + let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [environment.project]) } + let(:deployment_platform) { cluster.platform } + + it 'retrieves a namespace from the cluster' do + expect(cluster).to receive(:kubernetes_namespace_for) + .with(environment).and_return('mock-namespace') + + expect(subject).to eq 'mock-namespace' + end + end + end + describe '#terminals' do subject { environment.terminals } @@ -823,4 +851,35 @@ describe Environment, :use_clean_rails_memory_store_caching do subject.prometheus_adapter end end + + describe '#knative_services_finder' do + let(:environment) { create(:environment) } + + subject { environment.knative_services_finder } + + context 'environment has no deployments' do + it { is_expected.to be_nil } + end + + context 'environment has a deployment' do + let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } + + context 'with no cluster associated' do + let(:cluster) { nil } + + it { is_expected.to be_nil } + end + + context 'with a cluster associated' do + let(:cluster) { create(:cluster) } + + it 'calls the service finder' do + expect(Clusters::KnativeServicesFinder).to receive(:new) + .with(cluster, environment).and_return(:finder) + + is_expected.to eq :finder + end + end + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 7e9bbf5a407..1c41ceb7deb 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -23,6 +23,7 @@ describe Group do it { is_expected.to have_many(:badges).class_name('GroupBadge') } it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') } it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') } + it { is_expected.to have_many(:container_repositories) } describe '#members & #requesters' do let(:requester) { create(:user) } diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb index effd8b08124..8b53effe98f 100644 --- a/spec/models/lfs_download_object_spec.rb +++ b/spec/models/lfs_download_object_spec.rb @@ -50,7 +50,7 @@ describe LfsDownloadObject do before do allow(ApplicationSetting) .to receive(:current) - .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: setting)) + .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: setting)) end context 'are allowed' do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index a53add67066..e7dd7287a75 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -484,4 +484,12 @@ describe MergeRequestDiff do end end end + + describe '#lines_count' do + subject { diff_with_commits } + + it 'returns sum of all changed lines count in diff files' do + expect(subject.lines_count).to eq 109 + end + end end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index e9c7c94ad70..e5ac6ca65d6 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -105,10 +105,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do context 'manual configuration is enabled' do let(:manual_configuration) { true } - it 'returns rest client from api_url' do - expect(service.prometheus_client.url).to eq(api_url) - end - it 'calls valid?' do allow(service).to receive(:valid?).and_call_original diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 157103123ad..dde766c3813 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1156,7 +1156,6 @@ describe Project do describe '#pipeline_for' do let(:project) { create(:project, :repository) } - let!(:pipeline) { create_pipeline(project) } shared_examples 'giving the correct pipeline' do it { is_expected.to eq(pipeline) } @@ -1168,24 +1167,34 @@ describe Project do end end - context 'with explicit sha' do - subject { project.pipeline_for('master', pipeline.sha) } + context 'with a matching pipeline' do + let!(:pipeline) { create_pipeline(project) } + + context 'with explicit sha' do + subject { project.pipeline_for('master', pipeline.sha) } + + it_behaves_like 'giving the correct pipeline' - it_behaves_like 'giving the correct pipeline' + context 'with supplied id' do + let!(:other_pipeline) { create_pipeline(project) } - context 'with supplied id' do - let!(:other_pipeline) { create_pipeline(project) } + subject { project.pipeline_for('master', pipeline.sha, other_pipeline.id) } + + it { is_expected.to eq(other_pipeline) } + end + end - subject { project.pipeline_for('master', pipeline.sha, other_pipeline.id) } + context 'with implicit sha' do + subject { project.pipeline_for('master') } - it { is_expected.to eq(other_pipeline) } + it_behaves_like 'giving the correct pipeline' end end - context 'with implicit sha' do + context 'when there is no matching pipeline' do subject { project.pipeline_for('master') } - it_behaves_like 'giving the correct pipeline' + it { is_expected.to be_nil } end end @@ -1194,11 +1203,9 @@ describe Project do let!(:pipeline) { create_pipeline(project) } let!(:other_pipeline) { create_pipeline(project) } - context 'with implicit sha' do - subject { project.pipelines_for('master') } + subject { project.pipelines_for(project.default_branch, project.commit.sha) } - it { is_expected.to contain_exactly(pipeline, other_pipeline) } - end + it { is_expected.to contain_exactly(pipeline, other_pipeline) } end describe '#builds_enabled' do @@ -2587,45 +2594,33 @@ describe Project do end describe '#deployment_variables' do - context 'when project has no deployment service' do - let(:project) { create(:project) } + let(:project) { create(:project) } + let(:environment) { 'production' } - it 'returns an empty array' do - expect(project.deployment_variables).to eq [] - end + subject { project.deployment_variables(environment: environment) } + + before do + expect(project).to receive(:deployment_platform).with(environment: environment) + .and_return(deployment_platform) end - context 'when project uses mock deployment service' do - let(:project) { create(:mock_deployment_project) } + context 'when project has no deployment platform' do + let(:deployment_platform) { nil } - it 'returns an empty array' do - expect(project.deployment_variables).to eq [] - end + it { is_expected.to eq [] } end - context 'when project has a deployment service' do - context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + context 'when project has a deployment platform' do + let(:platform_variables) { %w(platform variables) } + let(:deployment_platform) { double } - it 'does not return variables from this service' do - expect(project.deployment_variables).not_to include( - { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false, masked: true } - ) - end + before do + expect(deployment_platform).to receive(:predefined_variables) + .with(project: project, environment_name: environment) + .and_return(platform_variables) end - context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token) } - let!(:cluster) { kubernetes_namespace.cluster } - let(:project) { kubernetes_namespace.project } - - it 'returns token from kubernetes namespace' do - expect(project.deployment_variables).to include( - { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } - ) - end - end + it { is_expected.to eq platform_variables } end end diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb index 3610408c138..a123ff5a2a6 100644 --- a/spec/models/prometheus_metric_spec.rb +++ b/spec/models/prometheus_metric_spec.rb @@ -150,4 +150,17 @@ describe PrometheusMetric do expect(subject.to_query_metric.queries).to eq(queries) end end + + describe '#to_metric_hash' do + it 'returns a hash suitable for inclusion on a metrics dashboard' do + expected_output = { + query_range: subject.query, + unit: subject.unit, + label: subject.legend, + metric_id: subject.id + } + + expect(subject.to_metric_hash).to eq(expected_output) + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 35c335c5b5c..46b86e8393d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -794,6 +794,24 @@ describe User do end end + describe '#accessible_deploy_keys' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:private_deploy_keys_project) { create(:deploy_keys_project) } + let!(:public_deploy_keys_project) { create(:deploy_keys_project) } + let!(:accessible_deploy_keys_project) { create(:deploy_keys_project, project: project) } + + before do + public_deploy_keys_project.deploy_key.update(public: true) + project.add_developer(user) + end + + it 'user can only see deploy keys accessible to right projects' do + expect(user.accessible_deploy_keys).to match_array([public_deploy_keys_project.deploy_key, + accessible_deploy_keys_project.deploy_key]) + end + end + describe '#deploy_keys' do include_context 'user keys' diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index eacf383be7d..95db2ba6a0d 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -28,24 +28,70 @@ describe BlobPresenter, :seed_helper do subject { described_class.new(blob) } it 'returns highlighted content' do - expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil) + expect(Gitlab::Highlight) + .to receive(:highlight) + .with( + 'files/ruby/regex.rb', + git_blob.data, + since: nil, + plain: nil, + language: nil + ) subject.highlight end it 'returns plain content when :plain is true' do - expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: true, language: nil) + expect(Gitlab::Highlight) + .to receive(:highlight) + .with( + 'files/ruby/regex.rb', + git_blob.data, + since: nil, + plain: true, + language: nil + ) subject.highlight(plain: true) end + context '"since" and "to" are present' do + before do + allow(git_blob) + .to receive(:data) + .and_return("line one\nline two\nline 3\nline 4") + end + + it 'returns limited highlighted content' do + expect(Gitlab::Highlight) + .to receive(:highlight) + .with( + 'files/ruby/regex.rb', + "line two\nline 3\n", + since: 2, + language: nil, + plain: nil + ) + + subject.highlight(since: 2, to: 3) + end + end + context 'gitlab-language contains a match' do before do allow(blob).to receive(:language_from_gitattributes).and_return('ruby') end it 'passes language to inner call' do - expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: 'ruby') + expect(Gitlab::Highlight) + .to receive(:highlight) + .with( + 'files/ruby/regex.rb', + git_blob.data, + since: nil, + plain: nil, + language: 'ruby' + ) subject.highlight end diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb index 001545bb5df..b4bf39f3cdb 100644 --- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb +++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb @@ -29,10 +29,6 @@ describe Projects::Settings::DeployKeysPresenter do it 'returns the enabled_keys size' do expect(presenter.enabled_keys_size).to eq(1) end - - it 'returns true if there is any enabled_keys' do - expect(presenter.any_keys_enabled?).to eq(true) - end end describe '#available_keys/#available_project_keys' do @@ -54,9 +50,5 @@ describe Projects::Settings::DeployKeysPresenter do it 'returns the available_project_keys size' do expect(presenter.available_project_keys_size).to eq(1) end - - it 'shows if there is an available key' do - expect(presenter.key_available?(deploy_key)).to eq(false) - end end end diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb new file mode 100644 index 00000000000..0a41e455d01 --- /dev/null +++ b/spec/requests/api/group_container_repositories_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::GroupContainerRepositories do + set(:group) { create(:group, :private) } + set(:project) { create(:project, :private, group: group) } + let(:reporter) { create(:user) } + let(:guest) { create(:user) } + + let(:root_repository) { create(:container_repository, :root, project: project) } + let(:test_repository) { create(:container_repository, project: project) } + + let(:users) do + { + anonymous: nil, + guest: guest, + reporter: reporter + } + end + + let(:api_user) { reporter } + + before do + group.add_reporter(reporter) + group.add_guest(guest) + + stub_feature_flags(container_registry_api: true) + stub_container_registry_config(enabled: true) + + root_repository + test_repository + end + + describe 'GET /groups/:id/registry/repositories' do + let(:url) { "/groups/#{group.id}/registry/repositories" } + + subject { get api(url, api_user) } + + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found + + it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do + let(:object) { group } + end + + context 'with invalid group id' do + let(:url) { '/groups/123412341234/registry/repositories' } + + it 'returns not found' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index e8ed016db69..a7b919de2ef 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -336,7 +336,6 @@ describe API::ProjectClusters do it 'does not update cluster attributes' do expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') - expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace') end it 'returns validation errors' do diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/project_container_repositories_spec.rb index b64f3ea1081..f1dc4e6f0b2 100644 --- a/spec/requests/api/container_registry_spec.rb +++ b/spec/requests/api/project_container_repositories_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::ContainerRegistry do +describe API::ProjectContainerRepositories do include ExclusiveLeaseHelpers set(:project) { create(:project, :private) } @@ -12,6 +12,16 @@ describe API::ContainerRegistry do let(:root_repository) { create(:container_repository, :root, project: project) } let(:test_repository) { create(:container_repository, project: project) } + let(:users) do + { + anonymous: nil, + developer: developer, + guest: guest, + maintainer: maintainer, + reporter: reporter + } + end + let(:api_user) { maintainer } before do @@ -27,57 +37,24 @@ describe API::ContainerRegistry do test_repository end - shared_examples 'being disallowed' do |param| - context "for #{param}" do - let(:api_user) { public_send(param) } - - it 'returns access denied' do - subject - - expect(response).to have_gitlab_http_status(:forbidden) - end - end - - context "for anonymous" do - let(:api_user) { nil } - - it 'returns not found' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - describe 'GET /projects/:id/registry/repositories' do - subject { get api("/projects/#{project.id}/registry/repositories", api_user) } - - it_behaves_like 'being disallowed', :guest - - context 'for reporter' do - let(:api_user) { reporter } - - it 'returns a list of repositories' do - subject + let(:url) { "/projects/#{project.id}/registry/repositories" } - expect(json_response.length).to eq(2) - expect(json_response.map { |repository| repository['id'] }).to contain_exactly( - root_repository.id, test_repository.id) - end + subject { get api(url, api_user) } - it 'returns a matching schema' do - subject + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('registry/repositories') - end + it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do + let(:object) { project } end end describe 'DELETE /projects/:id/registry/repositories/:repository_id' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) } - it_behaves_like 'being disallowed', :developer + it_behaves_like 'rejected container repository access', :developer, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for maintainer' do let(:api_user) { maintainer } @@ -96,7 +73,8 @@ describe API::ContainerRegistry do describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) } - it_behaves_like 'being disallowed', :guest + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for reporter' do let(:api_user) { reporter } @@ -124,10 +102,13 @@ describe API::ContainerRegistry do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params } - it_behaves_like 'being disallowed', :developer do + context 'disallowed' do let(:params) do { name_regex: 'v10.*' } end + + it_behaves_like 'rejected container repository access', :developer, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found end context 'for maintainer' do @@ -191,7 +172,8 @@ describe API::ContainerRegistry do describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } - it_behaves_like 'being disallowed', :guest + it_behaves_like 'rejected container repository access', :guest, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for reporter' do let(:api_user) { reporter } @@ -222,7 +204,8 @@ describe API::ContainerRegistry do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } - it_behaves_like 'being disallowed', :reporter + it_behaves_like 'rejected container repository access', :reporter, :forbidden + it_behaves_like 'rejected container repository access', :anonymous, :not_found context 'for developer' do let(:api_user) { developer } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5b3a2412aff..1d7ca85cdd2 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -838,6 +838,28 @@ describe API::Projects do end end + describe 'GET /users/:user_id/starred_projects/' do + before do + user3.update(starred_projects: [project, project2, project3]) + end + + it 'returns error when user not found' do + get api('/users/9999/starred_projects/') + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + + it 'returns projects filtered by user' do + get api("/users/#{user3.id}/starred_projects/", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id) + end + end + describe 'POST /projects/user/:id' do it 'creates new project without path but with name and return 201' do expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1) @@ -2148,6 +2170,85 @@ describe API::Projects do end end + describe 'GET /projects/:id/starrers' do + shared_examples_for 'project starrers response' do + it 'returns an array of starrers' do + get api("/projects/#{public_project.id}/starrers", current_user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response[0]['starred_since']).to be_present + expect(json_response[0]['user']).to be_present + end + + it 'returns the proper security headers' do + get api('/projects/1/starrers', current_user) + + expect(response).to include_security_headers + end + end + + let(:public_project) { create(:project, :public) } + let(:private_user) { create(:user, private_profile: true) } + + before do + user.update(starred_projects: [public_project]) + private_user.update(starred_projects: [public_project]) + end + + it 'returns not_found(404) for not existing project' do + get api("/projects/9999999999/starrers", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'public project without user' do + it_behaves_like 'project starrers response' do + let(:current_user) { nil } + end + + it 'returns only starrers with a public profile' do + get api("/projects/#{public_project.id}/starrers", nil) + + user_ids = json_response.map { |s| s['user']['id'] } + expect(user_ids).to include(user.id) + expect(user_ids).not_to include(private_user.id) + end + end + + context 'public project with user with private profile' do + it_behaves_like 'project starrers response' do + let(:current_user) { private_user } + end + + it 'returns current user with a private profile' do + get api("/projects/#{public_project.id}/starrers", private_user) + + user_ids = json_response.map { |s| s['user']['id'] } + expect(user_ids).to include(user.id, private_user.id) + end + end + + context 'private project' do + context 'with unauthorized user' do + it 'returns not_found for existing but unauthorized project' do + get api("/projects/#{project3.id}/starrers", user3) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'without user' do + it 'returns not_found for existing but unauthorized project' do + get api("/projects/#{project3.id}/starrers", nil) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + describe 'GET /projects/:id/languages' do context 'with an authorized user' do it_behaves_like 'languages and percentages JSON response' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 8a60980fe80..184c00a356a 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -25,6 +25,9 @@ describe API::Settings, 'Settings' do expect(json_response['ed25519_key_restriction']).to eq(0) expect(json_response['performance_bar_allowed_group_id']).to be_nil expect(json_response['instance_statistics_visibility_private']).to be(false) + expect(json_response['allow_local_requests_from_hooks_and_services']).to be(false) + expect(json_response['allow_local_requests_from_web_hooks_and_services']).to be(false) + expect(json_response['allow_local_requests_from_system_hooks']).to be(true) expect(json_response).not_to have_key('performance_bar_allowed_group_path') expect(json_response).not_to have_key('performance_bar_enabled') end @@ -67,7 +70,9 @@ describe API::Settings, 'Settings' do instance_statistics_visibility_private: true, diff_max_patch_bytes: 150_000, default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE, - local_markdown_version: 3 + local_markdown_version: 3, + allow_local_requests_from_web_hooks_and_services: true, + allow_local_requests_from_system_hooks: false } expect(response).to have_gitlab_http_status(200) @@ -95,6 +100,8 @@ describe API::Settings, 'Settings' do expect(json_response['diff_max_patch_bytes']).to eq(150_000) expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) expect(json_response['local_markdown_version']).to eq(3) + expect(json_response['allow_local_requests_from_web_hooks_and_services']).to eq(true) + expect(json_response['allow_local_requests_from_system_hooks']).to eq(false) end end @@ -117,6 +124,14 @@ describe API::Settings, 'Settings' do expect(json_response['performance_bar_allowed_group_id']).to be_nil end + it 'supports legacy allow_local_requests_from_hooks_and_services' do + put api("/application/settings", admin), + params: { allow_local_requests_from_hooks_and_services: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['allow_local_requests_from_hooks_and_services']).to eq(true) + end + context 'external policy classification settings' do let(:settings) do { diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb index dd5e43a4b62..c5b03bdd8c1 100644 --- a/spec/serializers/analytics_issue_entity_spec.rb +++ b/spec/serializers/analytics_issue_entity_spec.rb @@ -10,12 +10,12 @@ describe AnalyticsIssueEntity do id: "1", created_at: "2016-11-12 15:04:02.948604", author: user, - name: project.name, - path: project.namespace + project_path: project.path, + namespace_path: project.namespace.route.path } end - let(:project) { create(:project) } + let(:project) { create(:project, name: 'my project') } let(:request) { EntityRequest.new(entity: :merge_request) } let(:entity) do diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb index c9ffe1c5dad..9cb2ce13d12 100644 --- a/spec/serializers/analytics_issue_serializer_spec.rb +++ b/spec/serializers/analytics_issue_serializer_spec.rb @@ -8,7 +8,7 @@ describe AnalyticsIssueSerializer do end let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, name: 'my project') } let(:resource) do { total_time: "172802.724419", @@ -17,8 +17,8 @@ describe AnalyticsIssueSerializer do id: "1", created_at: "2016-11-12 15:04:02.948604", author: user, - name: project.name, - path: project.namespace + project_path: project.path, + namespace_path: project.namespace.route.path } end diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb index 123d7d795ce..a864051b2a3 100644 --- a/spec/serializers/analytics_merge_request_serializer_spec.rb +++ b/spec/serializers/analytics_merge_request_serializer_spec.rb @@ -8,7 +8,7 @@ describe AnalyticsMergeRequestSerializer do end let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, name: 'my project') } let(:resource) do { total_time: "172802.724419", @@ -18,8 +18,8 @@ describe AnalyticsMergeRequestSerializer do state: 'open', created_at: "2016-11-12 15:04:02.948604", author: user, - name: project.name, - path: project.namespace + project_path: project.path, + namespace_path: project.namespace.route.path } end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 77f108b6ab8..1b28d2d4d02 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -786,6 +786,50 @@ describe Ci::ProcessPipelineService, '#execute' do expect(builds.pending).to contain_exactly(deploy) end end + + context 'when one of the jobs is run on a failure' do + let!(:linux_notify) { create_build('linux:notify', stage: 'deploy', stage_idx: 2, when: 'on_failure') } + + let!(:linux_notify_on_build) { create(:ci_build_need, build: linux_notify, name: 'linux:build') } + + context 'when another job in build phase fails first' do + context 'when ci_dag_support is enabled' do + it 'does skip linux:notify' do + expect(process_pipeline).to be_truthy + + mac_build.reset.drop! + linux_build.reset.success! + + expect(linux_notify.reset).to be_skipped + end + end + + context 'when ci_dag_support is disabled' do + before do + stub_feature_flags(ci_dag_support: false) + end + + it 'does run linux:notify' do + expect(process_pipeline).to be_truthy + + mac_build.reset.drop! + linux_build.reset.success! + + expect(linux_notify.reset).to be_pending + end + end + end + + context 'when linux:build job fails first' do + it 'does run linux:notify' do + expect(process_pipeline).to be_truthy + + linux_build.reset.drop! + + expect(linux_notify.reset).to be_pending + end + end + end end def process_pipeline diff --git a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb new file mode 100644 index 00000000000..36c05469542 --- /dev/null +++ b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::BuildKubernetesNamespaceService do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:environment) { create(:environment) } + let(:project) { environment.project } + + let(:namespace_generator) { double(from_environment_slug: namespace) } + let(:namespace) { 'namespace' } + + subject { described_class.new(cluster, environment: environment).execute } + + before do + allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new).and_return(namespace_generator) + end + + shared_examples 'shared attributes' do + it 'initializes a new namespace and sets default values' do + expect(subject).to be_new_record + expect(subject.cluster).to eq cluster + expect(subject.project).to eq project + expect(subject.namespace).to eq namespace + expect(subject.service_account_name).to eq "#{namespace}-service-account" + end + end + + include_examples 'shared attributes' + + it 'sets cluster_project and environment' do + expect(subject.cluster_project).to eq cluster.cluster_project + expect(subject.environment).to eq environment + end + + context 'namespace per environment is disabled' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp, :namespace_per_environment_disabled) } + + include_examples 'shared attributes' + + it 'does not set environment' do + expect(subject.cluster_project).to eq cluster.cluster_project + expect(subject.environment).to be_nil + end + end + + context 'group cluster' do + let(:cluster) { create(:cluster, :group, :provided_by_gcp) } + + include_examples 'shared attributes' + + it 'does not set cluster_project' do + expect(subject.cluster_project).to be_nil + expect(subject.environment).to eq environment + end + end +end diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb index 44407ae2793..e44cc3f5a78 100644 --- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb @@ -9,8 +9,9 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d let(:platform) { cluster.platform } let(:api_url) { 'https://kubernetes.example.com' } let(:project) { cluster.project } + let(:environment) { create(:environment, project: project) } let(:cluster_project) { cluster.cluster_project } - let(:namespace) { "#{project.path}-#{project.id}" } + let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" } subject do described_class.new( @@ -79,7 +80,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d let(:kubernetes_namespace) do build(:cluster_kubernetes_namespace, cluster: cluster, - project: project) + project: project, + environment: environment) end it_behaves_like 'successful creation of kubernetes namespace' @@ -92,20 +94,22 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d build(:cluster_kubernetes_namespace, cluster: cluster, project: cluster_project.project, - cluster_project: cluster_project) + cluster_project: cluster_project, + environment: environment) end it_behaves_like 'successful creation of kubernetes namespace' end context 'when there is a Kubernetes Namespace associated' do - let(:namespace) { 'new-namespace' } + let(:namespace) { "new-namespace-#{environment.slug}" } let(:kubernetes_namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, project: cluster_project.project, - cluster_project: cluster_project) + cluster_project: cluster_project, + environment: environment) end before do diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index fb12877fa05..e3a728f2566 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -31,7 +31,159 @@ describe Issuable::BulkUpdateService do end end - context 'with project issuables' do + shared_examples 'updating labels' do + def create_issue_with_labels(labels) + create(:labeled_issue, project: project, labels: labels) + end + + let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) } + let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) } + let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) } + let(:issue_no_labels) { create(:issue, project: project) } + let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] } + + let(:labels) { [] } + let(:add_labels) { [] } + let(:remove_labels) { [] } + + let(:bulk_update_params) do + { + label_ids: labels.map(&:id), + add_label_ids: add_labels.map(&:id), + remove_label_ids: remove_labels.map(&:id) + } + end + + before do + bulk_update(issues, bulk_update_params) + end + + context 'when label_ids are passed' do + let(:issues) { [issue_all_labels, issue_no_labels] } + let(:labels) { [bug, regression] } + + it 'updates the labels of all issues passed to the labels passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id))) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + + context 'when those label IDs are empty' do + let(:labels) { [] } + + it 'updates the issues passed to have no labels' do + expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) + end + end + end + + context 'when add_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:add_labels) { [bug, regression, merge_requests] } + + it 'adds those label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when remove_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:remove_labels) { [bug, regression, merge_requests] } + + it 'removes those label IDs from all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when add_label_ids and remove_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:add_labels) { [bug] } + let(:remove_labels) { [merge_requests] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when add_label_ids and label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } + let(:labels) { [merge_requests] } + let(:add_labels) { [regression] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'does not update issues not passed in' do + expect(issue_no_labels.label_ids).to be_empty + end + end + + context 'when remove_label_ids and label_ids are passed' do + let(:issues) { [issue_no_labels, issue_bug_and_regression] } + let(:labels) { [merge_requests] } + let(:remove_labels) { [regression] } + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) + end + + it 'does not update issues not passed in' do + expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) + end + end + + context 'when add_label_ids, remove_label_ids, and label_ids are passed' do + let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } + let(:labels) { [regression] } + let(:add_labels) { [bug] } + let(:remove_labels) { [merge_requests] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + end + + context 'with issuables at a project level' do describe 'close issues' do let(:issues) { create_list(:issue, 2, project: project) } @@ -178,159 +330,11 @@ describe Issuable::BulkUpdateService do end describe 'updating labels' do - def create_issue_with_labels(labels) - create(:labeled_issue, project: project, labels: labels) - end - let(:bug) { create(:label, project: project) } let(:regression) { create(:label, project: project) } let(:merge_requests) { create(:label, project: project) } - let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) } - let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) } - let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) } - let(:issue_no_labels) { create(:issue, project: project) } - let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] } - - let(:labels) { [] } - let(:add_labels) { [] } - let(:remove_labels) { [] } - - let(:bulk_update_params) do - { - label_ids: labels.map(&:id), - add_label_ids: add_labels.map(&:id), - remove_label_ids: remove_labels.map(&:id) - } - end - - before do - bulk_update(issues, bulk_update_params) - end - - context 'when label_ids are passed' do - let(:issues) { [issue_all_labels, issue_no_labels] } - let(:labels) { [bug, regression] } - - it 'updates the labels of all issues passed to the labels passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id))) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - - context 'when those label IDs are empty' do - let(:labels) { [] } - - it 'updates the issues passed to have no labels' do - expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) - end - end - end - - context 'when add_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:add_labels) { [bug, regression, merge_requests] } - - it 'adds those label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when remove_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:remove_labels) { [bug, regression, merge_requests] } - - it 'removes those label IDs from all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when add_label_ids and remove_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:add_labels) { [bug] } - let(:remove_labels) { [merge_requests] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when add_label_ids and label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } - let(:labels) { [merge_requests] } - let(:add_labels) { [regression] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'does not update issues not passed in' do - expect(issue_no_labels.label_ids).to be_empty - end - end - - context 'when remove_label_ids and label_ids are passed' do - let(:issues) { [issue_no_labels, issue_bug_and_regression] } - let(:labels) { [merge_requests] } - let(:remove_labels) { [regression] } - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) - end - - it 'does not update issues not passed in' do - expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) - end - end - - context 'when add_label_ids, remove_label_ids, and label_ids are passed' do - let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } - let(:labels) { [regression] } - let(:add_labels) { [bug] } - let(:remove_labels) { [merge_requests] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end + it_behaves_like 'updating labels' end describe 'subscribe to issues' do @@ -360,7 +364,7 @@ describe Issuable::BulkUpdateService do end end - context 'with group issuables ' do + context 'with issuables at a group level' do let(:group) { create(:group) } describe 'updating milestones' do @@ -387,5 +391,18 @@ describe Issuable::BulkUpdateService do it_behaves_like 'updates milestones' end end + + describe 'updating labels' do + let(:project) { create(:project, :repository, group: group) } + let(:bug) { create(:group_label, group: group) } + let(:regression) { create(:group_label, group: group) } + let(:merge_requests) { create(:group_label, group: group) } + + before do + group.add_reporter(user) + end + + it_behaves_like 'updating labels' + end end end diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb new file mode 100644 index 00000000000..53b7497ae21 --- /dev/null +++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::CustomMetricEmbedService do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:user) { create(:user) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + let(:dashboard_path) { system_dashboard_path } + let(:group) { business_metric_title } + let(:title) { 'title' } + let(:y_label) { 'y_label' } + + describe '.valid_params?' do + let(:valid_params) do + { + embedded: true, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + end + + subject { described_class.valid_params?(params) } + + let(:params) { valid_params } + + it { is_expected.to be_truthy } + + context 'not embedded' do + let(:params) { valid_params.except(:embedded) } + + it { is_expected.to be_falsey } + end + + context 'non-system dashboard' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + + it { is_expected.to be_falsey } + end + + context 'undefined dashboard' do + let(:params) { valid_params.except(:dashboard_path) } + + it { is_expected.to be_truthy } + end + + context 'non-custom metric group' do + let(:group) { 'Different Group' } + + it { is_expected.to be_falsey } + end + + context 'missing group' do + let(:group) { nil } + + it { is_expected.to be_falsey } + end + + context 'missing title' do + let(:title) { nil } + + it { is_expected.to be_falsey } + end + + context 'undefined y-axis label' do + let(:params) { valid_params.except(:y_label) } + + it { is_expected.to be_falsey } + end + end + + describe '#get_dashboard' do + let(:service_params) do + [ + project, + user, + { + embedded: true, + environment: environment, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + ] + end + + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'misconfigured dashboard service response', :not_found + it_behaves_like 'raises error for users with insufficient permissions' + + context 'the custom metric exists' do + let!(:metric) { create(:prometheus_metric, project: project) } + + it_behaves_like 'valid embedded dashboard service response' + + it 'does not cache the unprocessed dashboard' do + expect(Gitlab::Metrics::Dashboard::Cache).not_to receive(:fetch) + + described_class.new(*service_params).get_dashboard + end + + context 'multiple metrics meet criteria' do + let!(:metric_2) { create(:prometheus_metric, project: project, query: 'avg(metric_2)') } + + it_behaves_like 'valid embedded dashboard service response' + + it 'includes both metrics' do + result = service_call + included_queries = all_queries(result[:dashboard]) + + expect(included_queries).to include('avg(metric_2)', 'avg(metric)') + end + end + end + + context 'when the metric exists in another project' do + let!(:metric) { create(:prometheus_metric, project: create(:project)) } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + end + + private + + def all_queries(dashboard) + dashboard[:panel_groups].flat_map do |group| + group[:panels].flat_map do |panel| + panel[:metrics].map do |metric| + metric[:query_range] + end + end + end + end +end diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb new file mode 100644 index 00000000000..a0f7315f750 --- /dev/null +++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + set(:project) { build(:project) } + set(:user) { create(:user) } + set(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + end + + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:group) { 'Group A' } + let(:title) { 'Super Chart A1' } + let(:y_label) { 'y_label' } + + describe '.valid_params?' do + let(:valid_params) do + { + embedded: true, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + end + + subject { described_class.valid_params?(params) } + + let(:params) { valid_params } + + it { is_expected.to be_truthy } + + context 'not embedded' do + let(:params) { valid_params.except(:embedded) } + + it { is_expected.to be_falsey } + end + + context 'undefined dashboard' do + let(:params) { valid_params.except(:dashboard_path) } + + it { is_expected.to be_truthy } + end + + context 'missing dashboard' do + let(:dashboard) { '' } + + it { is_expected.to be_truthy } + end + + context 'missing group' do + let(:group) { '' } + + it { is_expected.to be_falsey } + end + + context 'missing title' do + let(:title) { '' } + + it { is_expected.to be_falsey } + end + + context 'undefined y-axis label' do + let(:params) { valid_params.except(:y_label) } + + it { is_expected.to be_falsey } + end + end + + describe '#get_dashboard' do + let(:service_params) do + [ + project, + user, + { + environment: environment, + dashboard_path: dashboard_path, + group: group, + title: title, + y_label: y_label + } + ] + end + + let(:service_call) { described_class.new(*service_params).get_dashboard } + + context 'when the dashboard does not exist' do + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the dashboard is exists' do + let(:project) { project_with_dashboard(dashboard_path) } + + it_behaves_like 'valid embedded dashboard service response' + it_behaves_like 'raises error for users with insufficient permissions' + + it 'caches the unprocessed dashboard for subsequent calls' do + expect(YAML).to receive(:safe_load).once.and_call_original + + described_class.new(*service_params).get_dashboard + described_class.new(*service_params).get_dashboard + end + + context 'when the specified group is not present on the dashboard' do + let(:group) { 'Group Not Found' } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the specified title is not present on the dashboard' do + let(:title) { 'Title Not Found' } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + + context 'when the specified y-axis label is not present on the dashboard' do + let(:y_label) { 'Y-Axis Not Found' } + + it_behaves_like 'misconfigured dashboard service response', :not_found + end + end + + shared_examples 'uses system dashboard' do + it 'uses the default dashboard' do + expect(Gitlab::Metrics::Dashboard::Finder) + .to receive(:find_raw) + .with(project, dashboard_path: system_dashboard_path) + .once + + service_call + end + end + + context 'when the dashboard is nil' do + let(:dashboard_path) { nil } + + it_behaves_like 'uses system dashboard' + end + + context 'when the dashboard is not present' do + let(:dashboard_path) { '' } + + it_behaves_like 'uses system dashboard' + end + end +end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index e436af77ed4..9a6f64b825a 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -241,6 +241,18 @@ describe Projects::DestroyService do expect(destroy_project(project, user)).to be false end end + + context 'when registry is disabled' do + before do + stub_container_registry_config(enabled: false) + end + + it 'does not attempting to remove any tags' do + expect(Projects::ContainerRepository::DestroyService).not_to receive(:new) + + destroy_project(project, user) + end + end end context 'when there are tags for legacy root repository' do diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb index 80debcd3a7a..dabfd61d3f5 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb @@ -33,7 +33,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do before do allow(project).to receive(:lfs_enabled?).and_return(true) - response = instance_double(HTTParty::Response) + response = instance_double(Gitlab::HTTP::Response) allow(response).to receive(:body).and_return(objects_response.to_json) allow(response).to receive(:success?).and_return(true) allow(Gitlab::HTTP).to receive(:post).and_return(response) @@ -95,7 +95,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do shared_examples 'JSON parse errors' do |body| it 'raises error' do - response = instance_double(HTTParty::Response) + response = instance_double(Gitlab::HTTP::Response) allow(response).to receive(:body).and_return(body) allow(response).to receive(:success?).and_return(true) allow(Gitlab::HTTP).to receive(:post).and_return(response) diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index 75d534c59bf..970e82e7107 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -17,7 +17,7 @@ describe Projects::LfsPointers::LfsDownloadService do before do ApplicationSetting.create_from_defaults - stub_application_setting(allow_local_requests_from_hooks_and_services: local_request_setting) + stub_application_setting(allow_local_requests_from_web_hooks_and_services: local_request_setting) allow(project).to receive(:lfs_enabled?).and_return(true) end diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index 4bdb20de4c9..03bda94e9c6 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -131,7 +131,7 @@ describe Prometheus::ProxyService do allow(environment).to receive(:prometheus_adapter) .and_return(prometheus_adapter) allow(prometheus_adapter).to receive(:can_query?).and_return(true) - allow(prometheus_adapter).to receive(:prometheus_client_wrapper) + allow(prometheus_adapter).to receive(:prometheus_client) .and_return(prometheus_client) end diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb index a1e7aaf45f2..def20448bd9 100644 --- a/spec/services/self_monitoring/project/create_service_spec.rb +++ b/spec/services/self_monitoring/project/create_service_spec.rb @@ -30,15 +30,13 @@ describe SelfMonitoring::Project::CreateService do context 'with admin users' do let(:project) { result[:project] } + let(:group) { result[:group] } + let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } let!(:user) { create(:user, :admin) } before do - allow(ApplicationSetting) - .to receive(:current) - .and_return( - ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true) - ) + application_setting.allow_local_requests_from_web_hooks_and_services = true end shared_examples 'has prometheus service' do |listen_address| @@ -55,6 +53,15 @@ describe SelfMonitoring::Project::CreateService do it_behaves_like 'has prometheus service', 'http://localhost:9090' + it 'creates group' do + expect(result[:status]).to eq(:success) + expect(group).to be_persisted + expect(group.name).to eq(described_class::GROUP_NAME) + expect(group.path).to start_with(described_class::GROUP_PATH) + expect(group.path.split('-').last.length).to eq(8) + expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL) + end + it 'creates project with internal visibility' do expect(result[:status]).to eq(:success) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) @@ -62,7 +69,7 @@ describe SelfMonitoring::Project::CreateService do end it 'creates project with internal visibility even when internal visibility is restricted' do - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]) + application_setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::INTERNAL] expect(result[:status]).to eq(:success) expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) @@ -71,8 +78,8 @@ describe SelfMonitoring::Project::CreateService do it 'creates project with correct name and description' do expect(result[:status]).to eq(:success) - expect(project.name).to eq(described_class::DEFAULT_NAME) - expect(project.description).to eq(described_class::DEFAULT_DESCRIPTION) + expect(project.name).to eq(described_class::PROJECT_NAME) + expect(project.description).to eq(described_class::PROJECT_DESCRIPTION) end it 'adds all admins as maintainers' do @@ -81,25 +88,53 @@ describe SelfMonitoring::Project::CreateService do create(:user) expect(result[:status]).to eq(:success) - expect(project.owner).to eq(user) - expect(project.members.collect(&:user)).to contain_exactly(user, admin1, admin2) - expect(project.members.collect(&:access_level)).to contain_exactly( - Gitlab::Access::MAINTAINER, + expect(project.owner).to eq(group) + expect(group.members.collect(&:user)).to contain_exactly(user, admin1, admin2) + expect(group.members.collect(&:access_level)).to contain_exactly( + Gitlab::Access::OWNER, Gitlab::Access::MAINTAINER, Gitlab::Access::MAINTAINER ) end + it 'saves the project id' do + expect(result[:status]).to eq(:success) + expect(application_setting.instance_administration_project_id).to eq(project.id) + end + + it 'returns error when saving project ID fails' do + allow(application_setting).to receive(:update) { false } + + expect(result[:status]).to eq(:error) + expect(result[:failed_step]).to eq(:save_project_id) + expect(result[:message]).to eq('Could not save project ID') + end + + it 'does not fail when a project already exists' do + expect(result[:status]).to eq(:success) + + second_result = subject.execute + + expect(second_result[:status]).to eq(:success) + expect(second_result[:project]).to eq(project) + expect(second_result[:group]).to eq(group) + end + context 'when local requests from hooks and services are not allowed' do before do - allow(ApplicationSetting) - .to receive(:current) - .and_return( - ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: false) - ) + application_setting.allow_local_requests_from_web_hooks_and_services = false end it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'does not overwrite the existing whitelist' do + application_setting.outbound_local_requests_whitelist = ['example.com'] + + expect(result[:status]).to eq(:success) + expect(application_setting.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', 'localhost' + ) + end end context 'with non default prometheus address' do @@ -175,7 +210,7 @@ describe SelfMonitoring::Project::CreateService do expect(result).to eq({ status: :error, message: 'Could not add admins as members', - failed_step: :add_project_members + failed_step: :add_group_members }) end end diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 37bafc0c002..50167a2e059 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -19,17 +19,37 @@ describe WebHookService do let(:service_instance) { described_class.new(project_hook, data, :push_hooks) } describe '#initialize' do - it 'allow_local_requests is true if hook is a SystemHook' do - instance = described_class.new(build(:system_hook), data, :system_hook) - expect(instance.request_options[:allow_local_requests]).to be_truthy + before do + stub_application_setting(setting_name => setting) end - it 'allow_local_requests is false if hook is not a SystemHook' do - %i(project_hook service_hook web_hook_log).each do |hook| - instance = described_class.new(build(hook), data, hook) - expect(instance.request_options[:allow_local_requests]).to be_falsey + shared_examples_for 'respects outbound network setting' do + context 'when local requests are allowed' do + let(:setting) { true } + + it { expect(hook.request_options[:allow_local_requests]).to be_truthy } + end + + context 'when local requests are not allowed' do + let(:setting) { false } + + it { expect(hook.request_options[:allow_local_requests]).to be_falsey } end end + + context 'when SystemHook' do + let(:setting_name) { :allow_local_requests_from_system_hooks } + let(:hook) { described_class.new(build(:system_hook), data, :system_hook) } + + include_examples 'respects outbound network setting' + end + + context 'when ProjectHook' do + let(:setting_name) { :allow_local_requests_from_web_hooks_and_services } + let(:hook) { described_class.new(build(:project_hook), data, :project_hook) } + + include_examples 'respects outbound network setting' + end end describe '#execute' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6994b6687fc..bcc133790d1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -148,9 +148,9 @@ RSpec.configure do |config| Gitlab::ThreadMemoryCache.cache_backend.clear end - config.around(:example, :quarantine) do + config.around(:example, :quarantine) do |example| # Skip tests in quarantine unless we explicitly focus on them. - skip('In quarantine') unless config.inclusion_filter[:quarantine] + example.run if config.inclusion_filter[:quarantine] end config.before(:example, :request_store) do diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb index 1511a2f6b49..0e86b6dfda7 100644 --- a/spec/support/helpers/metrics_dashboard_helpers.rb +++ b/spec/support/helpers/metrics_dashboard_helpers.rb @@ -18,6 +18,14 @@ module MetricsDashboardHelpers project.repository.refresh_method_caches([:metrics_dashboard]) end + def system_dashboard_path + Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH + end + + def business_metric_title + PrometheusMetricEnums.group_details[:business][:group_title] + end + shared_examples_for 'misconfigured dashboard service response' do |status_code| it 'returns an appropriate message and status code' do result = service_call diff --git a/spec/support/matchers/be_url.rb b/spec/support/matchers/be_url.rb index 7bd0e7fada4..69171f53891 100644 --- a/spec/support/matchers/be_url.rb +++ b/spec/support/matchers/be_url.rb @@ -5,3 +5,7 @@ RSpec::Matchers.define :be_url do |_| URI.parse(actual) rescue false end end + +# looks better when used like: +# expect(thing).to receive(:method).with(a_valid_url) +RSpec::Matchers.alias_matcher :a_valid_url, :be_url diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 82582630dee..4e006edb7da 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -50,7 +50,7 @@ RSpec.shared_examples 'additional metrics query' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } let(:environment) { create(:environment, slug: 'environment-slug', project: project) } - let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) } + let(:kube_namespace) { environment.deployment_namespace } it_behaves_like 'query context containing environment slug and filter' diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb index 6ec8750ce87..27f6d0570b6 100644 --- a/spec/support/services/clusters/create_service_shared.rb +++ b/spec/support/services/clusters/create_service_shared.rb @@ -32,23 +32,56 @@ shared_context 'invalid cluster create params' do end shared_examples 'create cluster service success' do - it 'creates a cluster object and performs a worker' do - expect(ClusterProvisionWorker).to receive(:perform_async) - - expect { subject } - .to change { Clusters::Cluster.count }.by(1) - .and change { Clusters::Providers::Gcp.count }.by(1) - - expect(subject.name).to eq('test-cluster') - expect(subject.user).to eq(user) - expect(subject.project).to eq(project) - expect(subject.provider.gcp_project_id).to eq('gcp-project') - expect(subject.provider.zone).to eq('us-central1-a') - expect(subject.provider.num_nodes).to eq(1) - expect(subject.provider.machine_type).to eq('machine_type-a') - expect(subject.provider.access_token).to eq(access_token) - expect(subject.provider).to be_legacy_abac - expect(subject.platform).to be_nil + context 'namespace per environment feature is enabled' do + before do + stub_feature_flags(kubernetes_namespace_per_environment: true) + end + + it 'creates a cluster object and performs a worker' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { subject } + .to change { Clusters::Cluster.count }.by(1) + .and change { Clusters::Providers::Gcp.count }.by(1) + + expect(subject.name).to eq('test-cluster') + expect(subject.user).to eq(user) + expect(subject.project).to eq(project) + expect(subject.provider.gcp_project_id).to eq('gcp-project') + expect(subject.provider.zone).to eq('us-central1-a') + expect(subject.provider.num_nodes).to eq(1) + expect(subject.provider.machine_type).to eq('machine_type-a') + expect(subject.provider.access_token).to eq(access_token) + expect(subject.provider).to be_legacy_abac + expect(subject.platform).to be_nil + expect(subject.namespace_per_environment).to eq true + end + end + + context 'namespace per environment feature is disabled' do + before do + stub_feature_flags(kubernetes_namespace_per_environment: false) + end + + it 'creates a cluster object and performs a worker' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { subject } + .to change { Clusters::Cluster.count }.by(1) + .and change { Clusters::Providers::Gcp.count }.by(1) + + expect(subject.name).to eq('test-cluster') + expect(subject.user).to eq(user) + expect(subject.project).to eq(project) + expect(subject.provider.gcp_project_id).to eq('gcp-project') + expect(subject.provider.zone).to eq('us-central1-a') + expect(subject.provider.num_nodes).to eq(1) + expect(subject.provider.machine_type).to eq('machine_type-a') + expect(subject.provider.access_token).to eq(access_token) + expect(subject.provider).to be_legacy_abac + expect(subject.platform).to be_nil + expect(subject.namespace_per_environment).to eq false + end end end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index c11725c63d2..fd24c443288 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do read_group_merge_requests ] end - let(:reporter_permissions) { [:admin_label] } + let(:reporter_permissions) { %i[admin_label read_container_image] } let(:developer_permissions) { [:admin_milestone] } let(:maintainer_permissions) do %i[ diff --git a/spec/support/shared_examples/container_repositories_shared_examples.rb b/spec/support/shared_examples/container_repositories_shared_examples.rb new file mode 100644 index 00000000000..946b130fca2 --- /dev/null +++ b/spec/support/shared_examples/container_repositories_shared_examples.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +shared_examples 'rejected container repository access' do |user_type, status| + context "for #{user_type}" do + let(:api_user) { users[user_type] } + + it "returns #{status}" do + subject + + expect(response).to have_gitlab_http_status(status) + end + end +end + +shared_examples 'returns repositories for allowed users' do |user_type, scope| + context "for #{user_type}" do + it 'returns a list of repositories' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).not_to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + + context 'with tags param' do + let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" } + + before do + stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true) + stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true) + end + + it 'returns a list of repositories and their tags' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + end + end +end diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index fb22498f84f..26ed86bfe26 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -41,7 +41,15 @@ shared_examples 'issuable notes filter' do get :discussions, params: params.merge(notes_filter: notes_filter) - expect(user.reload.notes_filter_for(issuable)).to eq(0) + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) + end + + it 'does not set notes filter when persist_filter param is false' do + notes_filter = UserPreference::NOTES_FILTERS[:only_comments] + + get :discussions, params: params.merge(notes_filter: notes_filter, persist_filter: false) + + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) end it 'returns only user comments' do diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb index 1c53e2602eb..9837ba806db 100644 --- a/spec/support/shared_examples/relative_positioning_shared_examples.rb +++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb @@ -9,6 +9,12 @@ RSpec.shared_examples 'a class that supports relative positioning' do create(factory, params.merge(default_params)) end + def create_items_with_positions(positions) + positions.map do |position| + create_item(relative_position: position) + end + end + describe '.move_nulls_to_end' do it 'moves items with null relative_position to the end' do skip("#{item1} has a default relative position") if item1.relative_position @@ -104,46 +110,6 @@ RSpec.shared_examples 'a class that supports relative positioning' do end end - describe '#shift_after?' do - before do - [item1, item2].each do |item1| - item1.move_to_end && item1.save - end - end - - it 'returns true' do - item1.update(relative_position: item2.relative_position - 1) - - expect(item1.shift_after?).to be_truthy - end - - it 'returns false' do - item1.update(relative_position: item2.relative_position - 2) - - expect(item1.shift_after?).to be_falsey - end - end - - describe '#shift_before?' do - before do - [item1, item2].each do |item1| - item1.move_to_end && item1.save - end - end - - it 'returns true' do - item1.update(relative_position: item2.relative_position + 1) - - expect(item1.shift_before?).to be_truthy - end - - it 'returns false' do - item1.update(relative_position: item2.relative_position + 2) - - expect(item1.shift_before?).to be_falsey - end - end - describe '#move_between' do before do [item1, item2].each do |item1| @@ -257,5 +223,61 @@ RSpec.shared_examples 'a class that supports relative positioning' do expect(new_item.relative_position).to be(100) end + + it 'avoids N+1 queries when rebalancing other items' do + items = create_items_with_positions([100, 101, 102]) + + count = ActiveRecord::QueryRecorder.new do + new_item.move_between(items[-2], items[-1]) + end + + items = create_items_with_positions([150, 151, 152, 153, 154]) + + expect { new_item.move_between(items[-2], items[-1]) }.not_to exceed_query_limit(count) + end + end + + describe '#move_sequence_before' do + it 'moves the whole sequence of items to the middle of the nearest gap' do + items = create_items_with_positions([90, 100, 101, 102]) + + items.last.move_sequence_before + items.last.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([90, 95, 96, 102]) + end + + it 'finds a gap if there are unused positions' do + items = create_items_with_positions([100, 101, 102]) + + items.last.move_sequence_before + items.last.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([50, 51, 102]) + end + end + + describe '#move_sequence_after' do + it 'moves the whole sequence of items to the middle of the nearest gap' do + items = create_items_with_positions([100, 101, 102, 110]) + + items.first.move_sequence_after + items.first.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([100, 105, 106, 110]) + end + + it 'finds a gap if there are unused positions' do + items = create_items_with_positions([100, 101, 102]) + + items.first.move_sequence_after + items.first.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([100, 601, 602]) + end end end diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb index 16fceddb605..c5a775fefb6 100644 --- a/spec/support/shared_examples/url_validator_examples.rb +++ b/spec/support/shared_examples/url_validator_examples.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true RSpec.shared_examples 'url validator examples' do |schemes| - let(:validator) { described_class.new(attributes: [:link_url], **options) } - let!(:badge) { build(:badge, link_url: 'http://www.example.com') } + describe '#validate' do + let(:validator) { described_class.new(attributes: [:link_url], **options) } + let(:badge) { build(:badge, link_url: 'http://www.example.com') } - subject { validator.validate(badge) } + subject { validator.validate(badge) } - describe '#validate' do context 'with no options' do let(:options) { {} } @@ -42,3 +42,52 @@ RSpec.shared_examples 'url validator examples' do |schemes| end end end + +RSpec.shared_examples 'public url validator examples' do |setting| + let(:validator) { described_class.new(attributes: [:link_url]) } + let(:badge) { build(:badge, link_url: 'http://www.example.com') } + + subject { validator.validate(badge) } + + context 'by default' do + it 'blocks urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).to be_present + end + + it 'blocks urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).to be_present + end + end + + context 'when local requests are allowed' do + let!(:settings) { create(:application_setting) } + + before do + stub_application_setting(setting) + end + + it 'does not block urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).not_to be_present + end + + it 'does not block urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).not_to be_present + end + end +end diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb index f6364fb1dd5..3cbf1002730 100644 --- a/spec/validators/public_url_validator_spec.rb +++ b/spec/validators/public_url_validator_spec.rb @@ -2,27 +2,5 @@ require 'spec_helper' describe PublicUrlValidator do include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes] - - context 'by default' do - let(:validator) { described_class.new(attributes: [:link_url]) } - let!(:badge) { build(:badge, link_url: 'http://www.example.com') } - - subject { validator.validate(badge) } - - it 'blocks urls pointing to localhost' do - badge.link_url = 'https://127.0.0.1' - - subject - - expect(badge.errors).to be_present - end - - it 'blocks urls pointing to the local network' do - badge.link_url = 'https://192.168.1.1' - - subject - - expect(badge.errors).to be_present - end - end + include_examples 'public url validator examples', allow_local_requests_from_web_hooks_and_services: true end diff --git a/spec/validators/system_hook_url_validator_spec.rb b/spec/validators/system_hook_url_validator_spec.rb new file mode 100644 index 00000000000..02384bbd1ce --- /dev/null +++ b/spec/validators/system_hook_url_validator_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SystemHookUrlValidator do + include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes] + include_examples 'public url validator examples', allow_local_requests_from_system_hooks: true +end diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb new file mode 100644 index 00000000000..d2cd636f8c6 --- /dev/null +++ b/spec/views/search/_filter.html.haml_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'search/_filter' do + context 'when the search page is opened' do + it 'displays the correct elements' do + render + + expect(rendered).to have_selector('label[for="dashboard_search_group"]') + expect(rendered).to have_selector('button#dashboard_search_group') + + expect(rendered).to have_selector('label[for="dashboard_search_project"]') + expect(rendered).to have_selector('button#dashboard_search_project') + end + end +end diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb new file mode 100644 index 00000000000..69f40895d86 --- /dev/null +++ b/spec/views/search/_form.html.haml_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'search/_form' do + context 'when the search page is opened' do + it 'displays the correct elements' do + render + + expect(rendered).to have_selector('.search-field-holder.form-group') + expect(rendered).to have_selector('label[for="dashboard_search"]') + end + end +end diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb new file mode 100644 index 00000000000..483b913f2cc --- /dev/null +++ b/spec/views/search/show.html.haml_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'search/show' do + let(:search_term) { nil } + + before do + stub_template "search/_category.html.haml" => 'Category Partial' + stub_template "search/_results.html.haml" => 'Results Partial' + + @search_term = search_term + + render + end + + context 'when the search page is opened' do + it 'displays the title' do + expect(rendered).to have_selector('h1.page-title', text: 'Search') + expect(rendered).not_to have_selector('h1.page-title code') + end + + it 'does not render partials' do + expect(rendered).not_to render_template('search/_category') + expect(rendered).not_to render_template('search/_results') + end + end + + context 'when search term is supplied' do + let(:search_term) { 'Search Foo' } + + it 'renders partials' do + expect(rendered).to render_template('search/_category') + expect(rendered).to render_template('search/_results') + end + end +end diff --git a/spec/workers/build_process_worker_spec.rb b/spec/workers/build_process_worker_spec.rb index cceca40717c..d9a02ece142 100644 --- a/spec/workers/build_process_worker_spec.rb +++ b/spec/workers/build_process_worker_spec.rb @@ -10,7 +10,7 @@ describe BuildProcessWorker do it 'processes build' do expect_any_instance_of(Ci::Pipeline).to receive(:process!) - .with(build.name) + .with([build.id]) described_class.new.perform(build.id) end diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb index d33cf72e51e..ac677e3b555 100644 --- a/spec/workers/pipeline_process_worker_spec.rb +++ b/spec/workers/pipeline_process_worker_spec.rb @@ -12,6 +12,17 @@ describe PipelineProcessWorker do described_class.new.perform(pipeline.id) end + + context 'when build_ids are passed' do + let(:build) { create(:ci_build, pipeline: pipeline, name: 'my-build') } + + it 'processes pipeline with a list of builds' do + expect_any_instance_of(Ci::Pipeline).to receive(:process!) + .with([build.id]) + + described_class.new.perform(pipeline.id, [build.id]) + end + end end context 'when pipeline does not exist' do |