diff options
Diffstat (limited to 'spec')
259 files changed, 4540 insertions, 1207 deletions
diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb index b1ada3c99db..efb620fe6dd 100644 --- a/spec/config/object_store_settings_spec.rb +++ b/spec/config/object_store_settings_spec.rb @@ -3,7 +3,7 @@ require Rails.root.join('config', 'object_store_settings.rb') describe ObjectStoreSettings do describe '.parse' do - it 'should set correct default values' do + it 'sets correct default values' do settings = described_class.parse(nil) expect(settings['enabled']).to be false diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 9af472df74e..1a7be4c9a85 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -85,6 +85,13 @@ describe Admin::ApplicationSettingsController do expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end + + it 'updates the default_project_creation for string value' do + put :update, params: { application_setting: { default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } } + + expect(response).to redirect_to(admin_application_settings_path) + expect(ApplicationSetting.current.default_project_creation).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end end describe 'PUT #reset_registration_token' do diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb index 647fce0ecef..22165faa625 100644 --- a/spec/controllers/admin/groups_controller_spec.rb +++ b/spec/controllers/admin/groups_controller_spec.rb @@ -60,5 +60,11 @@ describe Admin::GroupsController do expect(response).to redirect_to(admin_group_path(group)) expect(group.users).not_to include group_user end + + it 'updates the project_creation_level successfully' do + expect do + post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } } + end.to change { group.reload.project_creation_level }.to(::Gitlab::Access::NO_ONE_PROJECT_ACCESS) + end end end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index ab40b4eb178..828de0e7ca5 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -75,7 +75,7 @@ describe Dashboard::MilestonesController do expect(response.body).not_to include(project_milestone.title) end - it 'should show counts of group and project milestones to which the user belongs to' do + it 'shows counts of group and project milestones to which the user belongs to' do get :index expect(response.body).to include("Open\n<span class=\"badge badge-pill\">2</span>") diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index ef23ffaa843..e5180ec5c5c 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -455,7 +455,7 @@ describe Groups::ClustersController do context 'when domain is invalid' do let(:domain) { 'http://not-a-valid-domain' } - it 'should not update cluster attributes' do + it 'does not update cluster attributes' do go cluster.reload diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 15eb0a442a6..3290ed8b088 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -124,7 +124,7 @@ describe Groups::Settings::CiCdController do end context 'when explicitly enabling auto devops' do - it 'should update group attribute' do + it 'updates group attribute' do expect(group.auto_devops_enabled).to eq(true) end end @@ -132,7 +132,7 @@ describe Groups::Settings::CiCdController do context 'when explicitly disabling auto devops' do let(:auto_devops_param) { '0' } - it 'should update group attribute' do + it 'updates group attribute' do expect(group.auto_devops_enabled).to eq(false) end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 38d7240ea81..4a28a27da79 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -349,6 +349,13 @@ describe GroupsController do expect(assigns(:group).errors).not_to be_empty expect(assigns(:group).path).not_to eq('new_path') end + + it 'updates the project_creation_level successfully' do + post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS } } + + expect(response).to have_gitlab_http_status(302) + expect(group.reload.project_creation_level).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end end describe '#ensure_canonical_path' do @@ -566,11 +573,11 @@ describe GroupsController do } end - it 'should return a notice' do + it 'returns a notice' do expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.") end - it 'should redirect to the new path' do + it 'redirects to the new path' do expect(response).to redirect_to("/#{new_parent_group.path}/#{group.path}") end end @@ -587,11 +594,11 @@ describe GroupsController do } end - it 'should return a notice' do + it 'returns a notice' do expect(flash[:notice]).to eq("Group '#{group.name}' was successfully transferred.") end - it 'should redirect to the new path' do + it 'redirects to the new path' do expect(response).to redirect_to("/#{group.path}") end end @@ -611,11 +618,11 @@ describe GroupsController do } end - it 'should return an alert' do + it 'returns an alert' do expect(flash[:alert]).to eq "Transfer failed: namespace directory cannot be moved" end - it 'should redirect to the current path' do + it 'redirects to the current path' do expect(response).to redirect_to(edit_group_path(group)) end end @@ -633,7 +640,7 @@ describe GroupsController do } end - it 'should be denied' do + it 'is denied' do expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 06c6f49f7cc..b823a8d7463 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -55,7 +55,7 @@ describe OmniauthCallbacksController, type: :controller do allow(@routes).to receive(:generate_extras) { [path, []] } end - it 'it calls through to the failure handler' do + it 'calls through to the failure handler' do request.env['omniauth.error'] = OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch") request.env['omniauth.error.strategy'] = OmniAuth::Strategies::SAML.new(nil) stub_route_as('/users/auth/saml/callback') diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 3801fca09dc..485e3e21c4d 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -10,6 +10,8 @@ describe Projects::BlobController do context 'with file path' do before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + get(:show, params: { namespace_id: project.namespace, @@ -285,7 +287,7 @@ describe Projects::BlobController do merge_request.update!(source_project: other_project, target_project: other_project) end - it "it redirect to blob" do + it "redirects to blob" do put :update, params: mr_params expect(response).to redirect_to(blob_after_edit_path) diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb index cfa010c2d1c..0b79484bbfa 100644 --- a/spec/controllers/projects/ci/lints_controller_spec.rb +++ b/spec/controllers/projects/ci/lints_controller_spec.rb @@ -16,15 +16,15 @@ describe Projects::Ci::LintsController do get :show, params: { namespace_id: project.namespace, project_id: project } end - it 'should be success' do + it 'is success' do expect(response).to be_success end - it 'should render show page' do + it 'renders show page' do expect(response).to render_template :show end - it 'should retrieve project' do + it 'retrieves project' do expect(assigns(:project)).to eq(project) end end @@ -36,7 +36,7 @@ describe Projects::Ci::LintsController do get :show, params: { namespace_id: project.namespace, project_id: project } end - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end @@ -74,7 +74,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'should be success' do + it 'is success' do expect(response).to be_success end @@ -82,7 +82,7 @@ describe Projects::Ci::LintsController do expect(response).to render_template :show end - it 'should retrieve project' do + it 'retrieves project' do expect(assigns(:project)).to eq(project) end end @@ -102,7 +102,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'should assign errors' do + it 'assigns errors' do expect(assigns[:error]).to eq('jobs:rubocop config contains unknown keys: scriptt') end end @@ -114,7 +114,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 8cb9130b834..9f753e5641f 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -15,7 +15,7 @@ describe Projects::CommitsController do describe "GET commits_root" do context "no ref is provided" do - it 'should redirect to the default branch of the project' do + it 'redirects to the default branch of the project' do get(:commits_root, params: { namespace_id: project.namespace, @@ -113,6 +113,8 @@ describe Projects::CommitsController do render_views before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original unless id.include?(' ') + get(:signatures, params: { namespace_id: project.namespace, diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb new file mode 100644 index 00000000000..5a0b92c2514 --- /dev/null +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Environments::PrometheusApiController do + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + set(:user) { create(:user) } + + before do + project.add_reporter(user) + sign_in(user) + end + + describe 'GET #proxy' do + let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) } + let(:expected_params) do + ActionController::Parameters.new( + environment_params( + proxy_path: 'query', + controller: 'projects/environments/prometheus_api', + action: 'proxy' + ) + ).permit! + end + + context 'with valid requests' do + before do + allow(Prometheus::ProxyService).to receive(:new) + .with(environment, 'GET', 'query', expected_params) + .and_return(prometheus_proxy_service) + + allow(prometheus_proxy_service).to receive(:execute) + .and_return(service_result) + end + + context 'with success result' do + let(:service_result) { { status: :success, body: prometheus_body } } + let(:prometheus_body) { '{"status":"success"}' } + let(:prometheus_json_body) { JSON.parse(prometheus_body) } + + it 'returns prometheus response' do + get :proxy, params: environment_params + + expect(Prometheus::ProxyService).to have_received(:new) + .with(environment, 'GET', 'query', expected_params) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq(prometheus_json_body) + end + end + + context 'with nil result' do + let(:service_result) { nil } + + it 'returns 202 accepted' do + get :proxy, params: environment_params + + expect(json_response['status']).to eq('processing') + expect(json_response['message']).to eq('Not ready yet. Try again later.') + expect(response).to have_gitlab_http_status(:accepted) + end + end + + context 'with 404 result' do + let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } } + + it 'returns body' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['body']).to eq('value') + end + end + + context 'with error result' do + context 'with http_status' do + let(:service_result) do + { http_status: :service_unavailable, status: :error, message: 'error message' } + end + + it 'sets the http response status code' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:service_unavailable) + expect(json_response['status']).to eq('error') + expect(json_response['message']).to eq('error message') + end + end + + context 'without http_status' do + let(:service_result) { { status: :error, message: 'error message' } } + + it 'returns bad_request' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['status']).to eq('error') + expect(json_response['message']).to eq('error message') + end + end + end + end + + context 'with inappropriate requests' do + context 'with anonymous user' do + before do + sign_out(user) + end + + it 'redirects to signin page' do + get :proxy, params: environment_params + + expect(response).to redirect_to(new_user_session_path) + end + end + + context 'without correct permissions' do + before do + project.team.truncate + end + + it 'returns 404' do + get :proxy, params: environment_params + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'with invalid environment id' do + let(:other_environment) { create(:environment) } + + it 'returns 404' do + get :proxy, params: environment_params(id: other_environment.id) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + private + + def environment_params(params = {}) + { + id: environment.id.to_s, + namespace_id: project.namespace.name, + project_id: project.name, + proxy_path: 'query', + query: '1' + }.merge(params) + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c8fa93a74ee..017162519d8 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -60,6 +60,8 @@ describe Projects::MergeRequestsController do end it "renders merge request page" do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + go(format: :html) expect(response).to be_success diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb index 86a12a5e903..f2b73956e8d 100644 --- a/spec/controllers/projects/mirrors_controller_spec.rb +++ b/spec/controllers/projects/mirrors_controller_spec.rb @@ -65,7 +65,7 @@ describe Projects::MirrorsController do expect(flash[:notice]).to match(/successfully updated/) end - it 'should create a RemoteMirror object' do + it 'creates a RemoteMirror object' do expect { do_put(project, remote_mirrors_attributes: remote_mirror_attributes) }.to change(RemoteMirror, :count).by(1) end end @@ -82,7 +82,7 @@ describe Projects::MirrorsController do expect(flash[:alert]).to match(/Only allowed protocols are/) end - it 'should not create a RemoteMirror object' do + it 'does not create a RemoteMirror object' do expect { do_put(project, remote_mirrors_attributes: remote_mirror_attributes) }.not_to change(RemoteMirror, :count) end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index b64ae552efc..814100f7d5d 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -97,6 +97,8 @@ describe Projects::PipelinesController do RequestStore.clear! RequestStore.begin! + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + expect { get_pipelines_index_json } .to change { Gitlab::GitalyClient.get_request_count }.by(2) end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 3cc3fe69fba..33486edcdd1 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::ProjectMembersController do let(:project) { create(:project, :public, :access_requestable) } describe 'GET index' do - it 'should have the project_members address with a 200 status code' do + it 'has the project_members address with a 200 status code' do get :index, params: { namespace_id: project.namespace, project_id: project } expect(response).to have_gitlab_http_status(200) diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index 62f2af947e4..0d0fa5d9f45 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -44,11 +44,15 @@ describe Projects::RefsController do end it 'renders JS' do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + xhr_get(:js) expect(response).to be_success end it 'renders JSON' do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + xhr_get(:json) expect(response).to be_success diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 276cf340962..782f5f272d9 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -76,6 +76,15 @@ describe Projects::Serverless::FunctionsController do end end + describe 'GET #metrics' do + context 'invalid data' do + it 'has a bad function name' do + get :metrics, params: params({ format: :json, environment_id: "*", id: "foo" }) + expect(response).to have_gitlab_http_status(204) + end + end + end + describe 'GET #index with data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 601a292bf54..d00d5bf579d 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -147,7 +147,7 @@ describe Projects::ServicesController do params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } } end - it 'should not update the service' do + it 'does not update the service' do service.reload expect(service.namespace).not_to eq('updated_namespace') end @@ -172,7 +172,7 @@ describe Projects::ServicesController do context 'with approved services' do let(:service_id) { 'jira' } - it 'should render edit page' do + it 'renders edit page' do expect(response).to be_success end end @@ -180,7 +180,7 @@ describe Projects::ServicesController do context 'with a deprecated service' do let(:service_id) { 'kubernetes' } - it 'should render edit page' do + it 'renders edit page' do expect(response).to be_success end end diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index b15a2bc84a5..78201498eaa 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -16,6 +16,8 @@ describe Projects::TreeController do render_views before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + get(:show, params: { namespace_id: project.namespace.to_param, diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 56d38b9475e..af437c5561b 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -77,6 +77,10 @@ describe ProjectsController do end context "user has access to project" do + before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + end + context "and does not have notification setting" do it "initializes notification as disabled" do get :show, params: { namespace_id: public_project.namespace, id: public_project } diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb index c71d75a3e7f..3cbbba934b8 100644 --- a/spec/controllers/user_callouts_controller_spec.rb +++ b/spec/controllers/user_callouts_controller_spec.rb @@ -14,11 +14,11 @@ describe UserCalloutsController do let(:feature_name) { UserCallout.feature_names.keys.first } context 'when callout entry does not exist' do - it 'should create a callout entry with dismissed state' do + it 'creates a callout entry with dismissed state' do expect { subject }.to change { UserCallout.count }.by(1) end - it 'should return success' do + it 'returns success' do subject expect(response).to have_gitlab_http_status(:ok) @@ -28,7 +28,7 @@ describe UserCalloutsController do context 'when callout entry already exists' do let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) } - it 'should return success' do + it 'returns success' do subject expect(response).to have_gitlab_http_status(:ok) @@ -39,7 +39,7 @@ describe UserCalloutsController do context 'with invalid feature name' do let(:feature_name) { 'bogus_feature_name' } - it 'should return bad request' do + it 'returns bad request' do subject expect(response).to have_gitlab_http_status(:bad_request) diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 067391c1179..f8c494c159e 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -336,6 +336,11 @@ FactoryBot.define do failure_reason 2 end + trait :prerequisite_failure do + failed + failure_reason 10 + end + trait :with_runner_session do after(:build) do |build| build.build_runner_session(url: 'https://localhost') diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index dcef8571f41..18a0c2ec731 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -4,6 +4,7 @@ FactoryBot.define do path { name.downcase.gsub(/\s/, '_') } type 'Group' owner nil + project_creation_level ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS after(:create) do |group| if group.owner diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index ea69ec0319b..4c6175f5590 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -345,7 +345,7 @@ describe 'Issue Boards', :js do click_link 'Create project label' - fill_in('new_label_name', with: 'Testing New Label') + fill_in('new_label_name', with: 'Testing New Label - with list') first('.suggest-colors a').click diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index ee38e756f9e..dfdb8d589eb 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -343,6 +343,24 @@ describe 'Issue Boards', :js do expect(page).to have_link 'test label' end + expect(page).to have_selector('.board', count: 3) + end + + it 'creates project label and list' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + click_link 'Create project label' + fill_in 'new_label_name', with: 'test label' + first('.suggest-colors-dropdown a').click + first('.js-add-list').click + click_button 'Create' + wait_for_requests + + expect(page).to have_link 'test label' + end + expect(page).to have_selector('.board', count: 4) end end diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 8d801161148..b2b3382666a 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -34,7 +34,7 @@ describe 'Expand and collapse diffs', :js do define_method(file.split('.').first) { file_container(file) } end - it 'should show the diff content with a highlighted line when linking to line' do + it 'shows the diff content with a highlighted line when linking to line' do expect(large_diff).not_to have_selector('.code') expect(large_diff).to have_selector('.nothing-here-block') @@ -48,7 +48,7 @@ describe 'Expand and collapse diffs', :js do expect(large_diff).to have_selector('.hll') end - it 'should show the diff content when linking to file' do + it 'shows the diff content when linking to file' do expect(large_diff).not_to have_selector('.code') expect(large_diff).to have_selector('.nothing-here-block') diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb index 8ed4051856e..56f6b1f7eaf 100644 --- a/spec/features/explore/groups_list_spec.rb +++ b/spec/features/explore/groups_list_spec.rb @@ -68,17 +68,17 @@ describe 'Explore Groups page', :js do end describe 'landing component' do - it 'should show a landing component' do + it 'shows a landing component' do expect(page).to have_content('Below you will find all the groups that are public.') end - it 'should be dismissable' do + it 'is dismissable' do find('.dismiss-button').click expect(page).not_to have_content('Below you will find all the groups that are public.') end - it 'should persistently not show once dismissed' do + it 'does not show persistently once dismissed' do find('.dismiss-button').click visit explore_groups_path diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index 2410cd92e3f..b661b5cbaef 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -69,7 +69,7 @@ describe 'User Cluster', :js do end it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + expect(page).to have_css('.gl-field-error') end end end diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 378e4d5febc..5cef5f0521f 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -77,6 +77,14 @@ describe 'Edit group settings' do end end + describe 'project creation level menu' do + it 'shows the selection menu' do + visit edit_group_path(group) + + expect(page).to have_content('Allowed to create projects') + end + end + describe 'edit group avatar' do before do visit edit_group_path(group) diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb index 0f793dbab6e..5b1a9512c55 100644 --- a/spec/features/groups/settings/ci_cd_spec.rb +++ b/spec/features/groups/settings/ci_cd_spec.rb @@ -43,7 +43,7 @@ describe 'Group CI/CD settings' do end context 'as owner first visiting group settings' do - it 'should see instance enabled badge' do + it 'sees instance enabled badge' do visit group_settings_ci_cd_path(group) page.within '#auto-devops-settings' do @@ -53,7 +53,7 @@ describe 'Group CI/CD settings' do end context 'when Auto DevOps group has been enabled' do - it 'should see group enabled badge' do + it 'sees group enabled badge' do group.update!(auto_devops_enabled: true) visit group_settings_ci_cd_path(group) @@ -65,7 +65,7 @@ describe 'Group CI/CD settings' do end context 'when Auto DevOps group has been disabled' do - it 'should not see a badge' do + it 'does not see a badge' do group.update!(auto_devops_enabled: false) visit group_settings_ci_cd_path(group) diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index c2f32c76422..8e7f78cab81 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -237,7 +237,7 @@ describe 'Group' do let!(:project) { create(:project, namespace: group) } let!(:path) { group_path(group) } - it 'it renders projects and groups on the page' do + it 'renders projects and groups on the page' do visit path wait_for_requests diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index e24b1f4349d..bcd2b90d3bb 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -82,15 +82,15 @@ describe 'Help Pages' do visit help_path end - it 'should display custom help page text' do + it 'displays custom help page text' do expect(page).to have_text "My Custom Text" end - it 'should hide marketing content when enabled' do + it 'hides marketing content when enabled' do expect(page).not_to have_link "Get a support subscription" end - it 'should use a custom support url' do + it 'uses a custom support url' do expect(page).to have_link "See our website for getting help", href: "http://example.com/help" end end diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index 23385ba65fc..870e92b8de8 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -70,7 +70,7 @@ describe "Internal references", :js do page.within("#merge-requests ul") do expect(page).to have_content(private_project_merge_request.title) - expect(page).to have_css(".merge-request-status") + expect(page).to have_css(".ic-issue-open-m") end expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}") diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index e0b1e286dee..75313442b65 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -42,7 +42,7 @@ describe 'Dropdown assignee', :js do expect(page).to have_css(js_dropdown_assignee, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do # We aren't using `input_filtered_search` because we want to see the loading indicator filtered_search.set('assignee:') @@ -51,13 +51,13 @@ describe 'Dropdown assignee', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do input_filtered_search('assignee:', submit: false, extra_space: false) expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading') end - it 'should load all the assignees when opened' do + it 'loads all the assignees when opened' do input_filtered_search('assignee:', submit: false, extra_space: false) expect(dropdown_assignee_size).to eq(4) diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index bedc61b9eed..bc8d9bc8450 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -50,7 +50,7 @@ describe 'Dropdown author', :js do expect(page).to have_css(js_dropdown_author, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do filtered_search.set('author:') @@ -58,13 +58,13 @@ describe 'Dropdown author', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do send_keys_to_filtered_search('author:') expect(page).not_to have_css('#js-dropdown-author .filter-dropdown-loading') end - it 'should load all the authors when opened' do + it 'loads all the authors when opened' do send_keys_to_filtered_search('author:') expect(dropdown_author_size).to eq(4) diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb index f36d4e8f23f..a5c3ab7e7d0 100644 --- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb @@ -69,7 +69,7 @@ describe 'Dropdown emoji', :js do expect(page).to have_css(js_dropdown_emoji, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do filtered_search.set('my-reaction:') @@ -77,13 +77,13 @@ describe 'Dropdown emoji', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do send_keys_to_filtered_search('my-reaction:') expect(page).not_to have_css('#js-dropdown-my-reaction .filter-dropdown-loading') end - it 'should load all the emojis when opened' do + it 'loads all the emojis when opened' do send_keys_to_filtered_search('my-reaction:') expect(dropdown_emoji_size).to eq(4) diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index b330eafe1d1..7584339ccc0 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -49,7 +49,7 @@ describe 'Dropdown milestone', :js do expect(page).to have_css(js_dropdown_milestone, visible: false) end - it 'should show loading indicator when opened' do + it 'shows loading indicator when opened' do slow_requests do filtered_search.set('milestone:') @@ -57,13 +57,13 @@ describe 'Dropdown milestone', :js do end end - it 'should hide loading indicator when loaded' do + it 'hides loading indicator when loaded' do filtered_search.set('milestone:') expect(find(js_dropdown_milestone)).not_to have_css('.filter-dropdown-loading') end - it 'should load all the milestones when opened' do + it 'loads all the milestones when opened' do filtered_search.set('milestone:') expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index f2e4c5779df..26c781350e5 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -45,7 +45,7 @@ describe 'New/edit issue', :js do wait_for_requests end - it 'should display selected users even if they are not part of the original API call' do + it 'displays selected users even if they are not part of the original API call' do find('.dropdown-input-field').native.send_keys user2.name page.within '.dropdown-menu-user' do diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb index 76bc93e9766..791bd003597 100644 --- a/spec/features/issues/issue_detail_spec.rb +++ b/spec/features/issues/issue_detail_spec.rb @@ -26,7 +26,7 @@ describe 'Issue Detail', :js do wait_for_requests end - it 'should encode the description to prevent xss issues' do + it 'encodes the description to prevent xss issues' do page.within('.issuable-details .detail-page-description') do expect(page).to have_selector('img', count: 1) expect(find('img')['onerror']).to be_nil diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 426e205b30b..9938a4e781c 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -43,7 +43,7 @@ describe 'Issues > User uses quick actions', :js do describe 'issue-only commands' do let(:user) { create(:user) } let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } + let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } before do project.add_maintainer(user) @@ -57,6 +57,8 @@ describe 'Issues > User uses quick actions', :js do end it_behaves_like 'confidential quick action' + it_behaves_like 'remove_due_date quick action' + it_behaves_like 'duplicate quick action' describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } @@ -76,24 +78,6 @@ describe 'Issues > User uses quick actions', :js do end end - describe 'removing a due date from note' do - let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } - - it_behaves_like 'remove_due_date action available and due date can be removed' - - context 'when the current user cannot update the due date' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it_behaves_like 'remove_due_date action not available' - end - end - describe 'toggling the WIP prefix from the title from note' do let(:issue) { create(:issue, project: project) } @@ -104,42 +88,6 @@ describe 'Issues > User uses quick actions', :js do end end - describe 'mark issue as duplicate' do - let(:issue) { create(:issue, project: project) } - let(:original_issue) { create(:issue, project: project) } - - context 'when the current user can update issues' do - it 'does not create a note, and marks the issue as a duplicate' do - add_note("/duplicate ##{original_issue.to_reference}") - - expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" - expect(page).to have_content 'Commands applied' - expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" - - expect(issue.reload).to be_closed - end - end - - context 'when the current user cannot update the issue' do - let(:guest) { create(:user) } - before do - project.add_guest(guest) - gitlab_sign_out - sign_in(guest) - visit project_issue_path(project, issue) - end - - it 'does not create a note, and does not mark the issue as a duplicate' do - add_note("/duplicate ##{original_issue.to_reference}") - - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" - - expect(issue.reload).to be_open - end - end - end - describe 'move the issue to another project' do let(:issue) { create(:issue, project: project) } diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 7c31e67a7fa..bac297de4a6 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -145,7 +145,7 @@ describe 'Labels Hierarchy', :js, :nested_groups do visit new_project_issue_path(project_1) end - it 'should be able to assign ancestor group labels' do + it 'is able to assign ancestor group labels' do fill_in 'issue_title', with: 'new created issue' fill_in 'issue_description', with: 'new issue description' diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb index 0ccab5b2fac..b8c4a78e24f 100644 --- a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb +++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb @@ -76,7 +76,7 @@ describe 'create a merge request, allowing commits from members who can merge to sign_in(member) end - it 'it hides the option from members' do + it 'hides the option from members' do visit edit_project_merge_request_path(target_project, merge_request) expect(page).not_to have_content('Allows commits from members who can merge to the target branch') diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 2609546990d..40ba676ff92 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -302,7 +302,7 @@ describe 'Merge request > User sees merge widget', :js do visit project_merge_request_path(project_only_mwps, merge_request_in_only_mwps_project) end - it 'should be allowed to merge' do + it 'is allowed to merge' do # Wait for the `ci_status` and `merge_check` requests wait_for_requests diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index 5c45e363997..6eae3fd4676 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -230,7 +230,7 @@ describe 'Merge request > User sees versions', :js do wait_for_requests end - it 'should only show diffs from the commit' do + it 'only shows diffs from the commit' do diff_commit_ids = find_all('.diff-file [data-commit-id]').map {|diff| diff['data-commit-id']} expect(diff_commit_ids).not_to be_empty diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index 56774896795..8d308729f62 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -57,130 +57,7 @@ describe 'Merge request > User uses quick actions', :js do end it_behaves_like 'merge quick action' - - describe 'toggling the WIP prefix in the title from note' do - context 'when the current user can toggle the WIP prefix' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - wait_for_requests - end - - it 'adds the WIP: prefix to the title' do - add_note("/wip") - - expect(page).not_to have_content '/wip' - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq true - end - - it 'removes the WIP: prefix from the title' do - merge_request.title = merge_request.wip_title - merge_request.save - add_note("/wip") - - expect(page).not_to have_content '/wip' - expect(page).to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq false - end - end - - context 'when the current user cannot toggle the WIP prefix' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not change the WIP prefix' do - add_note("/wip") - - expect(page).not_to have_content '/wip' - expect(page).not_to have_content 'Commands applied' - - expect(merge_request.reload.work_in_progress?).to eq false - end - end - end - - describe '/target_branch command in merge request' do - let(:another_project) { create(:project, :public, :repository) } - let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } - - before do - another_project.add_maintainer(user) - sign_in(user) - end - - it 'changes target_branch in new merge_request' do - visit project_new_merge_request_path(another_project, new_url_opts) - - fill_in "merge_request_title", with: 'My brand new feature' - fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" - click_button "Submit merge request" - - merge_request = another_project.merge_requests.first - expect(merge_request.description).to eq "le feature \nFeature description:" - expect(merge_request.target_branch).to eq 'fix' - end - - it 'does not change target branch when merge request is edited' do - new_merge_request = create(:merge_request, source_project: another_project) - - visit edit_project_merge_request_path(another_project, new_merge_request) - fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n" - click_button "Save changes" - - new_merge_request = another_project.merge_requests.first - expect(new_merge_request.description).to include('/target_branch') - expect(new_merge_request.target_branch).not_to eq('fix') - end - end - - describe '/target_branch command from note' do - context 'when the current user can change target branch' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it 'changes target branch from a note' do - add_note("message start \n/target_branch merge-test\n message end.") - - wait_for_requests - expect(page).not_to have_content('/target_branch') - expect(page).to have_content('message start') - expect(page).to have_content('message end.') - - expect(merge_request.reload.target_branch).to eq 'merge-test' - end - - it 'does not fail when target branch does not exists' do - add_note('/target_branch totally_not_existing_branch') - - expect(page).not_to have_content('/target_branch') - - expect(merge_request.target_branch).to eq 'feature' - end - end - - context 'when current user can not change target branch' do - before do - project.add_guest(guest) - sign_in(guest) - visit project_merge_request_path(project, merge_request) - end - - it 'does not change target branch' do - add_note('/target_branch merge-test') - - expect(page).not_to have_content '/target_branch merge-test' - - expect(merge_request.target_branch).to eq 'feature' - end - end - end + it_behaves_like 'target_branch quick action' + it_behaves_like 'wip quick action' end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index a7aa63018fd..aa2e538cc8e 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -572,7 +572,7 @@ describe 'File blob', :js do visit_blob('files/ruby/test.rb', ref: 'feature') end - it 'should show the realtime pipeline status' do + it 'shows the realtime pipeline status' do page.within('.commit-actions') do expect(page).to have_css('.ci-status-icon') expect(page).to have_css('.ci-status-icon-running') diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index aa1c3902f0f..527508b3519 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -80,7 +80,7 @@ describe 'Clusters Applications', :js do context 'on an abac cluster' do let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project]) } - it 'should show info block and not be installable' do + it 'shows info block and not be installable' do page.within('.js-cluster-application-row-knative') do expect(page).to have_css('.rbac-notice') expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') @@ -91,7 +91,7 @@ describe 'Clusters Applications', :js do context 'on an rbac cluster' do let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } - it 'should not show callout block and be installable' do + it 'does not show callout block and be installable' do page.within('.js-cluster-application-row-knative') do expect(page).not_to have_css('.rbac-notice') expect(page).to have_css('.js-cluster-application-install-button:not([disabled])') diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 9322e29d744..83e582c34f0 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -92,7 +92,7 @@ describe 'Gcp Cluster', :js do end it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + expect(page).to have_css('.gl-field-error') end end end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 1f2f7592d8b..fe4f737a7da 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -53,7 +53,7 @@ describe 'User Cluster', :js do end it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + expect(page).to have_css('.gl-field-error') end end end diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index 19f6ebf2c1a..614f11c8392 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -43,7 +43,7 @@ describe 'Mini Pipeline Graph in Commit View', :js do visit project_commit_path(project, project.commit.id) end - it 'should not display a mini pipeline graph' do + it 'does not display a mini pipeline graph' do expect(page).not_to have_selector('.mr-widget-pipeline-graph') end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index fe71cb7661a..da4ef6428d4 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -159,7 +159,7 @@ describe 'Environment' do context 'for project maintainer' do let(:role) { :maintainer } - it 'it shows the terminal button' do + it 'shows the terminal button' do expect(page).to have_terminal_button end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index b2a435e554d..7b7e45312d9 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -30,7 +30,7 @@ describe 'Environments page', :js do end describe 'in available tab page' do - it 'should show one environment' do + it 'shows one environment' do visit_environments(project, scope: 'available') expect(page).to have_css('.environments-container') @@ -44,7 +44,7 @@ describe 'Environments page', :js do create_list(:environment, 4, project: project, state: :available) end - it 'should render second page of pipelines' do + it 'renders second page of pipelines' do visit_environments(project, scope: 'available') find('.js-next-button').click @@ -56,7 +56,7 @@ describe 'Environments page', :js do end describe 'in stopped tab page' do - it 'should show no environments' do + it 'shows no environments' do visit_environments(project, scope: 'stopped') expect(page).to have_css('.environments-container') @@ -72,7 +72,7 @@ describe 'Environments page', :js do allow_any_instance_of(Kubeclient::Client).to receive(:proxy_url).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil)) end - it 'should show one environment without error' do + it 'shows one environment without error' do visit_environments(project, scope: 'available') expect(page).to have_css('.environments-container') @@ -87,7 +87,7 @@ describe 'Environments page', :js do end describe 'in available tab page' do - it 'should show no environments' do + it 'shows no environments' do visit_environments(project, scope: 'available') expect(page).to have_css('.environments-container') @@ -96,7 +96,7 @@ describe 'Environments page', :js do end describe 'in stopped tab page' do - it 'should show one environment' do + it 'shows one environment' do visit_environments(project, scope: 'stopped') expect(page).to have_css('.environments-container') diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index b2d2dba55f1..7432c600c1e 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -159,7 +159,7 @@ describe 'Project > Members > Invite group', :js do open_select2 '#link_group_id' end - it 'should infinitely scroll' do + it 'infinitely scrolls' do expect(find('.select2-drop .select2-results')).to have_selector('.select2-result', count: 1) scroll_select2_to_bottom('.select2-drop .select2-results:visible') diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 75c72a68069..b54ea929978 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -252,4 +252,23 @@ describe 'New project' do end end end + + context 'Namespace selector' do + context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + before do + group.add_developer(user) + visit new_project_path(namespace_id: group.id) + end + + it 'selects the group namespace' do + page.within('#blank-project-pane') do + namespace = find('#project_namespace_id option[selected]') + + expect(namespace.text).to eq group.full_path + end + end + end + end end diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb index ee6b67b2188..b1a705f09ce 100644 --- a/spec/features/projects/pipeline_schedules_spec.rb +++ b/spec/features/projects/pipeline_schedules_spec.rb @@ -93,14 +93,14 @@ describe 'Pipeline Schedules', :js do expect(page).to have_button('UTC') end - it 'it creates a new scheduled pipeline' do + it 'creates a new scheduled pipeline' do fill_in_schedule_form save_pipeline_schedule expect(page).to have_content('my fancy description') end - it 'it prevents an invalid form from being submitted' do + it 'prevents an invalid form from being submitted' do save_pipeline_schedule expect(page).to have_content('This field is required') @@ -112,7 +112,7 @@ describe 'Pipeline Schedules', :js do edit_pipeline_schedule end - it 'it displays existing properties' do + it 'displays existing properties' do description = find_field('schedule_description').value expect(description).to eq('pipeline schedule') expect(page).to have_button('master') diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index b197557039d..cf334e1e4da 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -154,7 +154,7 @@ describe 'Pipeline', :js do end end - it 'should be possible to retry the success job' do + it 'is possible to retry the success job' do find('#ci-badge-build .ci-action-icon-container').click expect(page).not_to have_content('Retry job') @@ -194,13 +194,13 @@ describe 'Pipeline', :js do end end - it 'should be possible to retry the failed build' do + it 'is possible to retry the failed build' do find('#ci-badge-test .ci-action-icon-container').click expect(page).not_to have_content('Retry job') end - it 'should include the failure reason' do + it 'includes the failure reason' do page.within('#ci-badge-test') do build_link = page.find('.js-pipeline-graph-job-link') expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)') @@ -220,7 +220,7 @@ describe 'Pipeline', :js do end end - it 'should be possible to play the manual job' do + it 'is possible to play the manual job' do find('#ci-badge-manual-build .ci-action-icon-container').click expect(page).not_to have_content('Play job') @@ -454,7 +454,7 @@ describe 'Pipeline', :js do expect(page).to have_content('Cancel running') end - it 'should not link to job' do + it 'does not link to job' do expect(page).not_to have_selector('.js-pipeline-graph-job-link') end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 7ca3b3d8edd..de780f13681 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -542,19 +542,19 @@ describe 'Pipelines', :js do visit_project_pipelines end - it 'should render a mini pipeline graph' do + it 'renders a mini pipeline graph' do expect(page).to have_selector('.js-mini-pipeline-graph') expect(page).to have_selector('.js-builds-dropdown-button') end context 'when clicking a stage badge' do - it 'should open a dropdown' do + it 'opens a dropdown' do find('.js-builds-dropdown-button').click expect(page).to have_link build.name end - it 'should be possible to cancel pending build' do + it 'is possible to cancel pending build' do find('.js-builds-dropdown-button').click find('.js-ci-action').click wait_for_requests @@ -570,7 +570,7 @@ describe 'Pipelines', :js do name: 'build') end - it 'should display the failure reason' do + it 'displays the failure reason' do find('.js-builds-dropdown-button').click within('.js-builds-dropdown-list') do @@ -587,21 +587,21 @@ describe 'Pipelines', :js do create(:ci_empty_pipeline, project: project) end - it 'should render pagination' do + it 'renders pagination' do visit project_pipelines_path(project) wait_for_requests expect(page).to have_selector('.gl-pagination') end - it 'should render second page of pipelines' do + it 'renders second page of pipelines' do visit project_pipelines_path(project, page: '2') wait_for_requests expect(page).to have_selector('.gl-pagination .page', count: 2) end - it 'should show updated content' do + it 'shows updated content' do visit project_pipelines_path(project) wait_for_requests page.find('.js-next-button .page-link').click @@ -685,7 +685,7 @@ describe 'Pipelines', :js do end it 'creates a new pipeline' do - expect { click_on 'Create pipeline' } + expect { click_on 'Run Pipeline' } .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last).to be_web @@ -698,7 +698,7 @@ describe 'Pipelines', :js do fill_in "Input variable value", with: "value" end - expect { click_on 'Create pipeline' } + expect { click_on 'Run Pipeline' } .to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) }) @@ -709,7 +709,7 @@ describe 'Pipelines', :js do context 'without gitlab-ci.yml' do before do - click_on 'Create pipeline' + click_on 'Run Pipeline' end it { expect(page).to have_content('Missing .gitlab-ci.yml file') } @@ -722,14 +722,14 @@ describe 'Pipelines', :js do click_link 'master' end - expect { click_on 'Create pipeline' } + expect { click_on 'Run Pipeline' } .to change { Ci::Pipeline.count }.by(1) end end end end - describe 'Create pipelines' do + describe 'Run Pipelines' do let(:project) { create(:project, :repository) } before do @@ -740,7 +740,7 @@ describe 'Pipelines', :js do it 'has field to add a new pipeline' do expect(page).to have_selector('.js-branch-select') expect(find('.js-branch-select')).to have_content project.default_branch - expect(page).to have_content('Create for') + expect(page).to have_content('Run for') end end diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb index aa71669de98..e14934b1672 100644 --- a/spec/features/projects/serverless/functions_spec.rb +++ b/spec/features/projects/serverless/functions_spec.rb @@ -50,7 +50,7 @@ describe 'Functions', :js do end it 'sees an empty listing of serverless functions' do - expect(page).to have_selector('.gl-responsive-table-row') + expect(page).to have_selector('.empty-state') end end end diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb index 84de6858d5f..b1c2bab08c0 100644 --- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb +++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb @@ -93,11 +93,13 @@ describe 'Projects > Settings > User manages merge request settings' do it 'when unchecked sets :printing_merge_request_link_enabled to false' do uncheck('project_printing_merge_request_link_enabled') within('.merge-request-settings-form') do + find('.qa-save-merge-request-changes') click_on('Save changes') end - # Wait for save to complete and page to reload + find('.flash-notice') checkbox = find_field('project_printing_merge_request_link_enabled') + expect(checkbox).not_to be_checked project.reload diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb index 9c1ef78b0ca..4e1e2f330ec 100644 --- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb +++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb @@ -23,14 +23,14 @@ describe 'Projects > Snippets > User comments on a snippet', :js do expect(page).to have_content('Good snippet!') end - it 'should have autocomplete' do + it 'has autocomplete' do find('#note_note').native.send_keys('') fill_in 'note[note]', with: '@' expect(page).to have_selector('.atwho-view') end - it 'should have zen mode' do + it 'has zen mode' do find('.js-zen-enter').click expect(page).to have_selector('.fullscreen') end diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb index 8d7e2883b2a..c0932539131 100644 --- a/spec/features/projects/user_creates_project_spec.rb +++ b/spec/features/projects/user_creates_project_spec.rb @@ -54,4 +54,31 @@ describe 'User creates a project', :js do expect(project.namespace).to eq(subgroup) end end + + context 'in a group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + before do + group.add_developer(user) + end + + it 'creates a new project' do + visit(new_project_path) + + fill_in :project_name, with: 'a-new-project' + fill_in :project_path, with: 'a-new-project' + + page.find('.js-select-namespace').click + page.find("div[role='option']", text: group.full_path).click + + page.within('#content-body') do + click_button('Create project') + end + + expect(page).to have_content("Project 'a-new-project' was successfully created") + + project = Project.find_by(name: 'a-new-project') + expect(project.namespace).to eq(group) + end + end end diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb index b0923b451ee..9a049764dec 100644 --- a/spec/features/raven_js_spec.rb +++ b/spec/features/raven_js_spec.rb @@ -3,13 +3,13 @@ require 'spec_helper' describe 'RavenJS' do let(:raven_path) { '/raven.chunk.js' } - it 'should not load raven if sentry is disabled' do + it 'does not load raven if sentry is disabled' do visit new_user_session_path expect(has_requested_raven).to eq(false) end - it 'should load raven if sentry is enabled' do + it 'loads raven if sentry is enabled' do stub_application_setting(clientside_sentry_dsn: 'https://key@domain.com/id', clientside_sentry_enabled: true) visit new_user_session_path diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index fc6726985ae..78e0a43ce6d 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -83,7 +83,7 @@ describe 'Comments on personal snippets', :js do expect(find('div#notes')).to have_content('This is awesome!') end - it 'should not have autocomplete' do + it 'does not have autocomplete' do wait_for_requests find('#note_note').native.send_keys('') diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb index 3db9ae7a951..bfa85696e19 100644 --- a/spec/features/users/overview_spec.rb +++ b/spec/features/users/overview_spec.rb @@ -93,7 +93,7 @@ describe 'Overview tab on a user profile', :js do describe 'user has no personal projects' do include_context 'visit overview tab' - it 'it shows an empty project list with an info message' do + it 'shows an empty project list with an info message' do page.within('.projects-block') do expect(page).to have_selector('.loading', visible: false) expect(page).to have_content('You haven\'t created any personal projects.') @@ -113,7 +113,7 @@ describe 'Overview tab on a user profile', :js do include_context 'visit overview tab' - it 'it shows one entry in the list of projects' do + it 'shows one entry in the list of projects' do page.within('.projects-block') do expect(page).to have_selector('.project-row', count: 1) end @@ -139,7 +139,7 @@ describe 'Overview tab on a user profile', :js do include_context 'visit overview tab' - it 'it shows max. ten entries in the list of projects' do + it 'shows max. ten entries in the list of projects' do page.within('.projects-block') do expect(page).to have_selector('.project-row', count: 10) end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 00b6cad1a66..fe53fabe54c 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -719,7 +719,7 @@ describe IssuesFinder do end end - describe '#use_subquery_for_search?' do + describe '#use_cte_for_search?' do let(:finder) { described_class.new(nil, params) } before do @@ -731,7 +731,7 @@ describe IssuesFinder do let(:params) { { attempt_group_search_optimizations: true } } it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end @@ -743,15 +743,15 @@ describe IssuesFinder do end it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end - context 'when the attempt_group_search_optimizations param is falsey' do + context 'when the force_cte param is falsey' do let(:params) { { search: 'foo' } } it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end @@ -763,80 +763,39 @@ describe IssuesFinder do end it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey + expect(finder.use_cte_for_search?).to be_falsey end end - context 'when force_cte? is true' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true, force_cte: true } } - - it 'returns false' do - expect(finder.use_subquery_for_search?).to be_falsey - end - end - - context 'when all conditions are met' do - let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } - - it 'returns true' do - expect(finder.use_subquery_for_search?).to be_truthy - end - end - end + context 'when attempt_group_search_optimizations is unset and attempt_project_search_optimizations is set' do + let(:params) { { search: 'foo', attempt_project_search_optimizations: true } } - describe '#use_cte_for_count?' do - let(:finder) { described_class.new(nil, params) } - - before do - allow(Gitlab::Database).to receive(:postgresql?).and_return(true) - stub_feature_flags(attempt_group_search_optimizations: true) - end - - context 'when there is no search param' do - let(:params) { { attempt_group_search_optimizations: true, force_cte: true } } - - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey - end - end - - context 'when the database is not Postgres' do - let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } - - before do - allow(Gitlab::Database).to receive(:postgresql?).and_return(false) - end - - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey - end - end - - context 'when the force_cte param is falsey' do - let(:params) { { search: 'foo' } } + context 'and the corresponding feature flag is disabled' do + before do + stub_feature_flags(attempt_project_search_optimizations: false) + end - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey + it 'returns false' do + expect(finder.use_cte_for_search?).to be_falsey + end end - end - context 'when the attempt_group_search_optimizations flag is disabled' do - let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } - - before do - stub_feature_flags(attempt_group_search_optimizations: false) - end + context 'and the corresponding feature flag is enabled' do + before do + stub_feature_flags(attempt_project_search_optimizations: true) + end - it 'returns false' do - expect(finder.use_cte_for_count?).to be_falsey + it 'returns true' do + expect(finder.use_cte_for_search?).to be_truthy + end end end context 'when all conditions are met' do - let(:params) { { search: 'foo', force_cte: true, attempt_group_search_optimizations: true } } + let(:params) { { search: 'foo', attempt_group_search_optimizations: true } } it 'returns true' do - expect(finder.use_cte_for_count?).to be_truthy + expect(finder.use_cte_for_search?).to be_truthy end end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 56136eb84bc..f508b9bdb6f 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -83,6 +83,14 @@ describe MergeRequestsFinder do expect(merge_requests).to contain_exactly(merge_request2) end + it 'filters by source project id' do + params = { source_project_id: merge_request2.source_project_id } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) + end + it 'filters by state' do params = { state: 'locked' } diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb index ecffbb9e197..34c7b508c56 100644 --- a/spec/finders/milestones_finder_spec.rb +++ b/spec/finders/milestones_finder_spec.rb @@ -9,7 +9,7 @@ describe MilestonesFinder do let!(:milestone_3) { create(:milestone, project: project_1, state: 'active', due_date: Date.tomorrow) } let!(:milestone_4) { create(:milestone, project: project_2, state: 'active') } - it 'it returns milestones for projects' do + it 'returns milestones for projects' do result = described_class.new(project_ids: [project_1.id, project_2.id], state: 'all').execute expect(result).to contain_exactly(milestone_3, milestone_4) diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb index 35279906854..3ad38207da4 100644 --- a/spec/finders/projects/serverless/functions_finder_spec.rb +++ b/spec/finders/projects/serverless/functions_finder_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Projects::Serverless::FunctionsFinder do include KubernetesHelpers + include PrometheusHelpers include ReactiveCachingHelpers let(:user) { create(:user) } @@ -24,12 +25,12 @@ describe Projects::Serverless::FunctionsFinder do describe 'retrieve data from knative' do it 'does not have knative installed' do - expect(described_class.new(project.clusters).execute).to be_empty + expect(described_class.new(project).execute).to be_empty end context 'has knative installed' do let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } - let(:finder) { described_class.new(project.clusters) } + let(:finder) { described_class.new(project) } it 'there are no functions' do expect(finder.execute).to be_empty @@ -58,13 +59,36 @@ describe Projects::Serverless::FunctionsFinder do expect(result).not_to be_empty expect(result["metadata"]["name"]).to be_eql(cluster.project.name) end + + it 'has metrics', :use_clean_rails_memory_store_caching do + end + end + + context 'has prometheus' do + let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } + let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + let(:finder) { described_class.new(project) } + + before do + allow(finder).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix')) + end + + it 'is available' do + expect(finder.has_prometheus?("*")).to be true + end + + it 'has query data' do + expect(finder.invocation_metrics("*", cluster.project.name)).not_to be_nil + end end end describe 'verify if knative is installed' do context 'knative is not installed' do it 'does not have knative installed' do - expect(described_class.new(project.clusters).installed?).to be false + expect(described_class.new(project).installed?).to be false end end @@ -72,7 +96,7 @@ describe Projects::Serverless::FunctionsFinder do let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } it 'does have knative installed' do - expect(described_class.new(project.clusters).installed?).to be true + expect(described_class.new(project).installed?).to be true end end end diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json index 7e9e048a9fd..214b67a9a0f 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json +++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json @@ -51,6 +51,5 @@ "toggle_subscription_path": { "type": "string" }, "move_issue_path": { "type": "string" }, "projects_autocomplete_path": { "type": "string" } - }, - "additionalProperties": false + } } diff --git a/spec/fixtures/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md index 2652a842c0e..555905bf07e 100644 --- a/spec/fixtures/blockquote_fence_after.md +++ b/spec/fixtures/blockquote_fence_after.md @@ -18,10 +18,13 @@ Double `>>>` inside code block: Blockquote outside code block: + > Quote + Code block inside blockquote: + > Quote > > ``` @@ -30,8 +33,10 @@ Code block inside blockquote: > > Quote + Single `>>>` inside code block inside blockquote: + > Quote > > ``` @@ -42,8 +47,10 @@ Single `>>>` inside code block inside blockquote: > > Quote + Double `>>>` inside code block inside blockquote: + > Quote > > ``` @@ -56,6 +63,7 @@ Double `>>>` inside code block inside blockquote: > > Quote + Single `>>>` inside HTML: <pre> @@ -76,10 +84,13 @@ Double `>>>` inside HTML: Blockquote outside HTML: + > Quote + HTML inside blockquote: + > Quote > > <pre> @@ -88,8 +99,10 @@ HTML inside blockquote: > > Quote + Single `>>>` inside HTML inside blockquote: + > Quote > > <pre> @@ -100,8 +113,10 @@ Single `>>>` inside HTML inside blockquote: > > Quote + Double `>>>` inside HTML inside blockquote: + > Quote > > <pre> @@ -113,3 +128,4 @@ Double `>>>` inside HTML inside blockquote: > </pre> > > Quote + diff --git a/spec/fixtures/valid.po b/spec/fixtures/valid.po index dbe2f952bad..155b6cbb95d 100644 --- a/spec/fixtures/valid.po +++ b/spec/fixtures/valid.po @@ -35,9 +35,6 @@ msgid_plural "%d pipelines" msgstr[0] "1 pipeline" msgstr[1] "%d pipelines" -msgid "A collection of graphs regarding Continuous Integration" -msgstr "Una colección de gráficos sobre Integración Continua" - msgid "About auto deploy" msgstr "Acerca del auto despliegue" diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js index 0d3dcc29f22..eea7bd87257 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/frontend/clusters/clusters_bundle_spec.js @@ -5,19 +5,41 @@ import { APPLICATION_STATUS, INGRESS_DOMAIN_SUFFIX, } from '~/clusters/constants'; -import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { loadHTMLFixture } from 'helpers/fixtures'; +import { setTestTimeout } from 'helpers/timeout'; +import $ from 'jquery'; describe('Clusters', () => { + setTestTimeout(500); + let cluster; - preloadFixtures('clusters/show_cluster.html'); + let mock; + + const mockGetClusterStatusRequest = () => { + const { statusPath } = document.querySelector('.js-edit-cluster-form').dataset; + + mock = new MockAdapter(axios); + + mock.onGet(statusPath).reply(200); + }; + + beforeEach(() => { + loadHTMLFixture('clusters/show_cluster.html'); + }); + + beforeEach(() => { + mockGetClusterStatusRequest(); + }); beforeEach(() => { - loadFixtures('clusters/show_cluster.html'); cluster = new Clusters(); }); afterEach(() => { cluster.destroy(); + mock.restore(); }); describe('toggle', () => { @@ -29,16 +51,13 @@ describe('Clusters', () => { '.js-cluster-enable-toggle-area .js-project-feature-toggle-input', ); - toggleButton.click(); - - getSetTimeoutPromise() - .then(() => { - expect(toggleButton.classList).not.toContain('is-checked'); + $(toggleInput).one('trigger-change', () => { + expect(toggleButton.classList).not.toContain('is-checked'); + expect(toggleInput.getAttribute('value')).toEqual('false'); + done(); + }); - expect(toggleInput.getAttribute('value')).toEqual('false'); - }) - .then(done) - .catch(done.fail); + toggleButton.click(); }); }); @@ -197,7 +216,7 @@ describe('Clusters', () => { describe('installApplication', () => { it('tries to install helm', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); @@ -209,7 +228,7 @@ describe('Clusters', () => { }); it('tries to install ingress', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); expect(cluster.store.state.applications.ingress.requestStatus).toEqual(null); @@ -221,7 +240,7 @@ describe('Clusters', () => { }); it('tries to install runner', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); expect(cluster.store.state.applications.runner.requestStatus).toEqual(null); @@ -233,7 +252,7 @@ describe('Clusters', () => { }); it('tries to install jupyter', () => { - spyOn(cluster.service, 'installApplication').and.returnValue(Promise.resolve()); + jest.spyOn(cluster.service, 'installApplication').mockResolvedValueOnce(); expect(cluster.store.state.applications.jupyter.requestStatus).toEqual(null); cluster.installApplication({ @@ -248,35 +267,32 @@ describe('Clusters', () => { }); }); - it('sets error request status when the request fails', done => { - spyOn(cluster.service, 'installApplication').and.returnValue( - Promise.reject(new Error('STUBBED ERROR')), - ); + it('sets error request status when the request fails', () => { + jest + .spyOn(cluster.service, 'installApplication') + .mockRejectedValueOnce(new Error('STUBBED ERROR')); expect(cluster.store.state.applications.helm.requestStatus).toEqual(null); - cluster.installApplication({ id: 'helm' }); + const promise = cluster.installApplication({ id: 'helm' }); expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_SUBMITTED); expect(cluster.store.state.applications.helm.requestReason).toEqual(null); expect(cluster.service.installApplication).toHaveBeenCalled(); - getSetTimeoutPromise() - .then(() => { - expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE); - expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); - }) - .then(done) - .catch(done.fail); + return promise.then(() => { + expect(cluster.store.state.applications.helm.requestStatus).toEqual(REQUEST_FAILURE); + expect(cluster.store.state.applications.helm.requestReason).toBeDefined(); + }); }); }); describe('handleSuccess', () => { beforeEach(() => { - spyOn(cluster.store, 'updateStateFromServer'); - spyOn(cluster, 'toggleIngressDomainHelpText'); - spyOn(cluster, 'checkForNewInstalls'); - spyOn(cluster, 'updateContainer'); + jest.spyOn(cluster.store, 'updateStateFromServer').mockReturnThis(); + jest.spyOn(cluster, 'toggleIngressDomainHelpText').mockReturnThis(); + jest.spyOn(cluster, 'checkForNewInstalls').mockReturnThis(); + jest.spyOn(cluster, 'updateContainer').mockReturnThis(); cluster.handleSuccess({ data: {} }); }); @@ -300,9 +316,13 @@ describe('Clusters', () => { describe('toggleIngressDomainHelpText', () => { const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS; + let ingressPreviousState; + let ingressNewState; - const ingressPreviousState = { status: INSTALLABLE }; - const ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + beforeEach(() => { + ingressPreviousState = { status: INSTALLABLE }; + ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' }; + }); describe(`when ingress application new status is ${INSTALLED}`, () => { beforeEach(() => { @@ -333,7 +353,7 @@ describe('Clusters', () => { }); describe('when ingress application new status and old status are the same', () => { - it('does not modify custom domain help text', () => { + it('does not display custom domain help text', () => { ingressPreviousState.status = INSTALLED; ingressNewState.status = ingressPreviousState.status; @@ -342,5 +362,15 @@ describe('Clusters', () => { expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); }); }); + + describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => { + it('does not display custom domain help text', () => { + ingressNewState.externalIp = null; + + cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState); + + expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true); + }); + }); }); }); diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js index a2dd4e93daf..b28d0075d06 100644 --- a/spec/javascripts/clusters/components/application_row_spec.js +++ b/spec/frontend/clusters/components/application_row_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import eventHub from '~/clusters/event_hub'; import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '~/clusters/constants'; import applicationRow from '~/clusters/components/application_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; import { DEFAULT_APPLICATION_STATE } from '../services/mock_data'; describe('Application Row', () => { @@ -160,7 +160,7 @@ describe('Application Row', () => { }); it('clicking install button emits event', () => { - spyOn(eventHub, '$emit'); + jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLABLE, @@ -176,7 +176,7 @@ describe('Application Row', () => { }); it('clicking install button when installApplicationRequestParams are provided emits event', () => { - spyOn(eventHub, '$emit'); + jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLABLE, @@ -193,7 +193,7 @@ describe('Application Row', () => { }); it('clicking disabled install button emits nothing', () => { - spyOn(eventHub, '$emit'); + jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.INSTALLING, @@ -255,7 +255,7 @@ describe('Application Row', () => { }); it('clicking upgrade button emits event', () => { - spyOn(eventHub, '$emit'); + jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATE_ERRORED, @@ -271,7 +271,7 @@ describe('Application Row', () => { }); it('clicking disabled upgrade button emits nothing', () => { - spyOn(eventHub, '$emit'); + jest.spyOn(eventHub, '$emit'); vm = mountComponent(ApplicationRow, { ...DEFAULT_APPLICATION_STATE, status: APPLICATION_STATUS.UPDATING, diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js index 0f8153ad493..7c54a27d950 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/frontend/clusters/components/applications_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import applications from '~/clusters/components/applications.vue'; import { CLUSTER_TYPE } from '~/clusters/constants'; import eventHub from '~/clusters/event_hub'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent from 'helpers/vue_mount_component_helper'; import { APPLICATIONS_MOCK_STATE } from '../services/mock_data'; describe('Applications', () => { @@ -314,7 +314,7 @@ describe('Applications', () => { }); it('emits event when clicking Save changes button', () => { - spyOn(eventHub, '$emit'); + jest.spyOn(eventHub, '$emit'); vm = mountComponent(Applications, props); const saveButton = vm.$el.querySelector('.js-knative-save-domain-button'); diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/frontend/clusters/services/mock_data.js index b4d1bb710e0..b4d1bb710e0 100644 --- a/spec/javascripts/clusters/services/mock_data.js +++ b/spec/frontend/clusters/services/mock_data.js diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index 161722ec571..161722ec571 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js new file mode 100644 index 00000000000..2e8bff298c4 --- /dev/null +++ b/spec/frontend/helpers/monitor_helper_spec.js @@ -0,0 +1,45 @@ +import * as monitorHelper from '~/helpers/monitor_helper'; + +describe('monitor helper', () => { + const defaultConfig = { default: true, name: 'default name' }; + const name = 'data name'; + const series = [[1, 1], [2, 2], [3, 3]]; + const data = ({ metric = { default_name: name }, values = series } = {}) => [{ metric, values }]; + + describe('makeDataSeries', () => { + const expectedDataSeries = [ + { + ...defaultConfig, + data: series, + }, + ]; + + it('converts query results to data series', () => { + expect(monitorHelper.makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual( + expectedDataSeries, + ); + }); + + it('returns an empty array if no query results exist', () => { + expect(monitorHelper.makeDataSeries([], defaultConfig)).toEqual([]); + }); + + it('handles multi-series query results', () => { + const expectedData = { ...expectedDataSeries[0], name: 'default name: data name' }; + + expect(monitorHelper.makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([ + expectedData, + expectedData, + ]); + }); + + it('excludes NaN values', () => { + expect( + monitorHelper.makeDataSeries( + data({ metric: {}, values: [[1, 1], [2, NaN]] }), + defaultConfig, + ), + ).toEqual([{ ...expectedDataSeries[0], data: [[1, 1]] }]); + }); + }); +}); diff --git a/spec/frontend/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js index 5de7a281d34..40d47aaad03 100644 --- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js +++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js @@ -18,7 +18,7 @@ describe('IDE commit module mutations', () => { describe('UPDATE_COMMIT_ACTION', () => { it('updates commitAction', () => { - mutations.UPDATE_COMMIT_ACTION(state, 'testing'); + mutations.UPDATE_COMMIT_ACTION(state, { commitAction: 'testing' }); expect(state.commitAction).toBe('testing'); }); @@ -39,4 +39,20 @@ describe('IDE commit module mutations', () => { expect(state.submitCommitLoading).toBeTruthy(); }); }); + + describe('TOGGLE_SHOULD_CREATE_MR', () => { + it('changes shouldCreateMR to true when initial state is false', () => { + state.shouldCreateMR = false; + mutations.TOGGLE_SHOULD_CREATE_MR(state); + + expect(state.shouldCreateMR).toBe(true); + }); + + it('changes shouldCreateMR to false when initial state is true', () => { + state.shouldCreateMR = true; + mutations.TOGGLE_SHOULD_CREATE_MR(state); + + expect(state.shouldCreateMR).toBe(false); + }); + }); }); diff --git a/spec/frontend/labels_select_spec.js b/spec/frontend/labels_select_spec.js index acfdc885032..d54e0eab845 100644 --- a/spec/frontend/labels_select_spec.js +++ b/spec/frontend/labels_select_spec.js @@ -13,40 +13,104 @@ const mockLabels = [ }, ]; +const mockScopedLabels = [ + { + id: 27, + title: 'Foo::Bar', + description: 'Foobar', + color: '#333ABC', + text_color: '#FFFFFF', + }, +]; + describe('LabelsSelect', () => { describe('getLabelTemplate', () => { - const label = mockLabels[0]; - let $labelEl; - - beforeEach(() => { - $labelEl = $( - LabelsSelect.getLabelTemplate({ - labels: mockLabels, - issueUpdateURL: mockUrl, - }), - ); - }); + describe('when normal label is present', () => { + const label = mockLabels[0]; + let $labelEl; - it('generated label item template has correct label URL', () => { - expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label'); - }); + beforeEach(() => { + $labelEl = $( + LabelsSelect.getLabelTemplate({ + labels: mockLabels, + issueUpdateURL: mockUrl, + enableScopedLabels: true, + scopedLabelsDocumentationLink: 'docs-link', + }), + ); + }); - it('generated label item template has correct label title', () => { - expect($labelEl.find('span.label').text()).toBe(label.title); - }); + it('generated label item template has correct label URL', () => { + expect($labelEl.attr('href')).toBe('/foo/bar?label_name[]=Foo%20Label'); + }); - it('generated label item template has label description as title attribute', () => { - expect($labelEl.find('span.label').attr('title')).toBe(label.description); - }); + it('generated label item template has correct label title', () => { + expect($labelEl.find('span.label').text()).toBe(label.title); + }); + + it('generated label item template has label description as title attribute', () => { + expect($labelEl.find('span.label').attr('title')).toBe(label.description); + }); - it('generated label item template has correct label styles', () => { - expect($labelEl.find('span.label').attr('style')).toBe( - `background-color: ${label.color}; color: ${label.text_color};`, - ); + it('generated label item template has correct label styles', () => { + expect($labelEl.find('span.label').attr('style')).toBe( + `background-color: ${label.color}; color: ${label.text_color};`, + ); + }); + + it('generated label item has a badge class', () => { + expect($labelEl.find('span').hasClass('badge')).toEqual(true); + }); + + it('generated label item template does not have scoped-label class', () => { + expect($labelEl.find('.scoped-label')).toHaveLength(0); + }); }); - it('generated label item has a badge class', () => { - expect($labelEl.find('span').hasClass('badge')).toEqual(true); + describe('when scoped label is present', () => { + const label = mockScopedLabels[0]; + let $labelEl; + + beforeEach(() => { + $labelEl = $( + LabelsSelect.getLabelTemplate({ + labels: mockScopedLabels, + issueUpdateURL: mockUrl, + enableScopedLabels: true, + scopedLabelsDocumentationLink: 'docs-link', + }), + ); + }); + + it('generated label item template has correct label URL', () => { + expect($labelEl.find('a').attr('href')).toBe('/foo/bar?label_name[]=Foo%3A%3ABar'); + }); + + it('generated label item template has correct label title', () => { + expect($labelEl.find('span.label').text()).toBe(label.title); + }); + + it('generated label item template has html flag as true', () => { + expect($labelEl.find('span.label').attr('data-html')).toBe('true'); + }); + + it('generated label item template has question icon', () => { + expect($labelEl.find('i.fa-question-circle')).toHaveLength(1); + }); + + it('generated label item template has scoped-label class', () => { + expect($labelEl.find('.scoped-label')).toHaveLength(1); + }); + + it('generated label item template has correct label styles', () => { + expect($labelEl.find('span.label').attr('style')).toBe( + `background-color: ${label.color}; color: ${label.text_color};`, + ); + }); + + it('generated label item has a badge class', () => { + expect($labelEl.find('span').hasClass('badge')).toEqual(true); + }); }); }); }); diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 0a266b19ea5..3f331055a32 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -151,4 +151,31 @@ describe('text_utility', () => { ); }); }); + + describe('truncateNamespace', () => { + it(`should return the root namespace if the namespace only includes one level`, () => { + expect(textUtils.truncateNamespace('a / b')).toBe('a'); + }); + + it(`should return the first 2 namespaces if the namespace inlcudes exactly 2 levels`, () => { + expect(textUtils.truncateNamespace('a / b / c')).toBe('a / b'); + }); + + it(`should return the first and last namespaces, separated by "...", if the namespace inlcudes more than 2 levels`, () => { + expect(textUtils.truncateNamespace('a / b / c / d')).toBe('a / ... / c'); + expect(textUtils.truncateNamespace('a / b / c / d / e / f / g / h / i')).toBe('a / ... / h'); + }); + + it(`should return an empty string for invalid inputs`, () => { + [undefined, null, 4, {}, true, new Date()].forEach(input => { + expect(textUtils.truncateNamespace(input)).toBe(''); + }); + }); + + it(`should not alter strings that aren't formatted as namespaces`, () => { + ['', ' ', '\t', 'a', 'a \\ b'].forEach(input => { + expect(textUtils.truncateNamespace(input)).toBe(input); + }); + }); + }); }); diff --git a/spec/frontend/serverless/components/area_spec.js b/spec/frontend/serverless/components/area_spec.js new file mode 100644 index 00000000000..62005e1981a --- /dev/null +++ b/spec/frontend/serverless/components/area_spec.js @@ -0,0 +1,122 @@ +import { shallowMount } from '@vue/test-utils'; +import Area from '~/serverless/components/area.vue'; +import { mockNormalizedMetrics } from '../mock_data'; + +describe('Area component', () => { + const mockWidgets = 'mockWidgets'; + const mockGraphData = mockNormalizedMetrics; + let areaChart; + + beforeEach(() => { + areaChart = shallowMount(Area, { + propsData: { + graphData: mockGraphData, + containerWidth: 0, + }, + slots: { + default: mockWidgets, + }, + sync: false, + }); + }); + + afterEach(() => { + areaChart.destroy(); + }); + + it('renders chart title', () => { + expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); + }); + + it('contains graph widgets from slot', () => { + expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + const mockDate = mockNormalizedMetrics.queries[0].result[0].values[0].time; + const generateSeriesData = type => ({ + seriesData: [ + { + componentSubType: type, + value: [mockDate, 4], + }, + ], + value: mockDate, + }); + + describe('series is of line type', () => { + beforeEach(() => { + areaChart.vm.formatTooltipText(generateSeriesData('line')); + }); + + it('formats tooltip title', () => { + expect(areaChart.vm.tooltipPopoverTitle).toBe('28 Feb 2019, 11:11AM'); + }); + + it('formats tooltip content', () => { + expect(areaChart.vm.tooltipPopoverContent).toBe('Invocations (requests): 4'); + }); + }); + + it('verify default interval value of 1', () => { + expect(areaChart.vm.getInterval).toBe(1); + }); + }); + + describe('onResize', () => { + const mockWidth = 233; + + beforeEach(() => { + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ + width: mockWidth, + })); + areaChart.vm.onResize(); + }); + + it('sets area chart width', () => { + expect(areaChart.vm.width).toBe(mockWidth); + }); + }); + }); + + describe('computed', () => { + describe('chartData', () => { + it('utilizes all data points', () => { + expect(Object.keys(areaChart.vm.chartData)).toEqual(['requests']); + expect(areaChart.vm.chartData.requests.length).toBe(2); + }); + + it('creates valid data', () => { + const data = areaChart.vm.chartData.requests; + + expect( + data.filter( + datum => new Date(datum.time).getTime() > 0 && typeof datum.value === 'number', + ).length, + ).toBe(data.length); + }); + }); + + describe('generateSeries', () => { + it('utilizes correct time data', () => { + expect(areaChart.vm.generateSeries.data).toEqual([ + ['2019-02-28T11:11:38.756Z', 0], + ['2019-02-28T11:12:38.756Z', 0], + ]); + }); + }); + + describe('xAxisLabel', () => { + it('constructs a label for the chart x-axis', () => { + expect(areaChart.vm.xAxisLabel).toBe('invocations / minute'); + }); + }); + + describe('yAxisLabel', () => { + it('constructs a label for the chart y-axis', () => { + expect(areaChart.vm.yAxisLabel).toBe('Invocations (requests)'); + }); + }); + }); +}); diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js index bdf7a714910..161a637dd75 100644 --- a/spec/javascripts/serverless/components/environment_row_spec.js +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -1,81 +1,70 @@ -import Vue from 'vue'; - import environmentRowComponent from '~/serverless/components/environment_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import ServerlessStore from '~/serverless/stores/serverless_store'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; +import { translate } from '~/serverless/utils'; -const createComponent = (env, envName) => - mountComponent(Vue.extend(environmentRowComponent), { env, envName }); +const createComponent = (localVue, env, envName) => + shallowMount(environmentRowComponent, { localVue, propsData: { env, envName }, sync: false }).vm; describe('environment row component', () => { describe('default global cluster case', () => { + let localVue; let vm; beforeEach(() => { - const store = new ServerlessStore(false, '/cluster_path', 'help_path'); - store.updateFunctionsFromServer(mockServerlessFunctions); - vm = createComponent(store.state.functions['*'], '*'); + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctions)['*'], '*'); }); + afterEach(() => vm.$destroy()); + it('has the correct envId', () => { expect(vm.envId).toEqual('env-global'); - vm.$destroy(); }); it('is open by default', () => { expect(vm.isOpenClass).toEqual({ 'is-open': true }); - vm.$destroy(); }); it('generates correct output', () => { - expect(vm.$el.querySelectorAll('li').length).toEqual(2); expect(vm.$el.id).toEqual('env-global'); expect(vm.$el.classList.contains('is-open')).toBe(true); expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); - - vm.$destroy(); }); it('opens and closes correctly', () => { expect(vm.isOpen).toBe(true); vm.toggleOpen(); - Vue.nextTick(() => { - expect(vm.isOpen).toBe(false); - }); - vm.$destroy(); + expect(vm.isOpen).toBe(false); }); }); describe('default named cluster case', () => { let vm; + let localVue; beforeEach(() => { - const store = new ServerlessStore(false, '/cluster_path', 'help_path'); - store.updateFunctionsFromServer(mockServerlessFunctionsDiffEnv); - vm = createComponent(store.state.functions.test, 'test'); + localVue = createLocalVue(); + vm = createComponent(localVue, translate(mockServerlessFunctionsDiffEnv).test, 'test'); }); + afterEach(() => vm.$destroy()); + it('has the correct envId', () => { expect(vm.envId).toEqual('env-test'); - vm.$destroy(); }); it('is open by default', () => { expect(vm.isOpenClass).toEqual({ 'is-open': true }); - vm.$destroy(); }); it('generates correct output', () => { - expect(vm.$el.querySelectorAll('li').length).toEqual(1); expect(vm.$el.id).toEqual('env-test'); expect(vm.$el.classList.contains('is-open')).toBe(true); expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); - - vm.$destroy(); }); }); }); diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js new file mode 100644 index 00000000000..31348ff1194 --- /dev/null +++ b/spec/frontend/serverless/components/function_details_spec.js @@ -0,0 +1,117 @@ +import Vuex from 'vuex'; + +import functionDetailsComponent from '~/serverless/components/function_details.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; + +describe('functionDetailsComponent', () => { + let localVue; + let component; + let store; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + describe('Verify base functionality', () => { + const serviceStub = { + name: 'test', + description: 'a description', + environment: '*', + url: 'http://service.com/test', + namespace: 'test-ns', + podcount: 0, + metricsUrl: '/metrics', + }; + + it('has a name, description, URL, and no pods loaded', () => { + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + sync: false, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-name').innerHTML.trim(), + ).toContain('test'); + + expect( + component.vm.$el.querySelector('.serverless-function-description').innerHTML.trim(), + ).toContain('a description'); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain( + 'No pods loaded at this time.', + ); + }); + + it('has a pods loaded', () => { + serviceStub.podcount = 1; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('1 pod in use'); + }); + + it('has multiple pods loaded', () => { + serviceStub.podcount = 3; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('p').innerHTML.trim()).toContain('3 pods in use'); + }); + + it('can support a missing description', () => { + serviceStub.description = null; + + component = shallowMount(functionDetailsComponent, { + localVue, + store, + propsData: { + func: serviceStub, + hasPrometheus: false, + clustersPath: '/clusters', + helpPath: '/help', + }, + sync: false, + }); + + expect( + component.vm.$el.querySelector('.serverless-function-description').querySelector('div') + .innerHTML.length, + ).toEqual(0); + }); + }); +}); diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/frontend/serverless/components/function_row_spec.js index 6933a8f6c87..414fdc5cd82 100644 --- a/spec/javascripts/serverless/components/function_row_spec.js +++ b/spec/frontend/serverless/components/function_row_spec.js @@ -1,11 +1,10 @@ -import Vue from 'vue'; - import functionRowComponent from '~/serverless/components/function_row.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; import { mockServerlessFunction } from '../mock_data'; -const createComponent = func => mountComponent(Vue.extend(functionRowComponent), { func }); +const createComponent = func => + shallowMount(functionRowComponent, { propsData: { func }, sync: false }).vm; describe('functionRowComponent', () => { it('Parses the function details correctly', () => { @@ -13,10 +12,7 @@ describe('functionRowComponent', () => { expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); - expect(vm.$el.querySelector('time').getAttribute('data-original-title')).not.toBe(null); - expect(vm.$el.querySelector('div.url-text-field').innerHTML).toEqual( - mockServerlessFunction.url, - ); + expect(vm.$el.querySelector('timeago-stub').getAttribute('time')).not.toBe(null); vm.$destroy(); }); @@ -25,8 +21,6 @@ describe('functionRowComponent', () => { const vm = createComponent(mockServerlessFunction); expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row - expect(vm.checkClass(vm.$el.querySelector('svg'))).toBe(false); // check a button image - expect(vm.checkClass(vm.$el.querySelector('div.url-text-field'))).toBe(false); // check the url bar vm.$destroy(); }); diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js new file mode 100644 index 00000000000..5533de1a70a --- /dev/null +++ b/spec/frontend/serverless/components/functions_spec.js @@ -0,0 +1,106 @@ +import Vuex from 'vuex'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import functionsComponent from '~/serverless/components/functions.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createStore } from '~/serverless/store'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('functionsComponent', () => { + let component; + let store; + let localVue; + + beforeEach(() => { + localVue = createLocalVue(); + localVue.use(Vuex); + + store = createStore(); + }); + + afterEach(() => { + component.vm.$destroy(); + }); + + it('should render empty state when Knative is not installed', () => { + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: false, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('emptystate-stub')).not.toBe(null); + }); + + it('should render a loading component', () => { + store.dispatch('requestFunctionsLoading'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect(component.vm.$el.querySelector('glloadingicon-stub')).not.toBe(null); + }); + + it('should render empty state when there is no function data', () => { + store.dispatch('receiveFunctionsNoDataSuccess'); + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: '', + helpPath: '', + statusPath: '', + }, + sync: false, + }); + + expect( + component.vm.$el + .querySelector('.empty-state, .js-empty-state') + .classList.contains('js-empty-state'), + ).toBe(true); + + expect(component.vm.$el.querySelector('.state-title, .text-center').innerHTML.trim()).toEqual( + 'No functions available', + ); + }); + + fit('should render the functions list', () => { + const statusPath = 'statusPath'; + const axiosMock = new AxiosMockAdapter(axios); + axiosMock.onGet(statusPath).reply(200); + + component = shallowMount(functionsComponent, { + localVue, + store, + propsData: { + installed: true, + clustersPath: 'clustersPath', + helpPath: 'helpPath', + statusPath, + }, + sync: false, + }); + + component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions); + + return component.vm.$nextTick().then(() => { + expect(component.vm.$el.querySelector('environmentrow-stub')).not.toBe(null); + }); + }); +}); diff --git a/spec/frontend/serverless/components/missing_prometheus_spec.js b/spec/frontend/serverless/components/missing_prometheus_spec.js new file mode 100644 index 00000000000..d0df6125290 --- /dev/null +++ b/spec/frontend/serverless/components/missing_prometheus_spec.js @@ -0,0 +1,38 @@ +import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = missingData => + shallowMount(missingPrometheusComponent, { + propsData: { + clustersPath: '/clusters', + helpPath: '/help', + missingData, + }, + sync: false, + }).vm; + +describe('missingPrometheusComponent', () => { + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + it('should render missing prometheus message', () => { + vm = createComponent(false); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Function invocation metrics require Prometheus to be installed first.', + ); + + expect(vm.$el.querySelector('glbutton-stub').getAttribute('variant')).toEqual('success'); + }); + + it('should render no prometheus data message', () => { + vm = createComponent(true); + + expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain( + 'Invocation metrics loading or not available at this time.', + ); + }); +}); diff --git a/spec/frontend/serverless/components/pod_box_spec.js b/spec/frontend/serverless/components/pod_box_spec.js new file mode 100644 index 00000000000..d82825d8f62 --- /dev/null +++ b/spec/frontend/serverless/components/pod_box_spec.js @@ -0,0 +1,23 @@ +import podBoxComponent from '~/serverless/components/pod_box.vue'; +import { shallowMount } from '@vue/test-utils'; + +const createComponent = count => + shallowMount(podBoxComponent, { + propsData: { + count, + }, + sync: false, + }).vm; + +describe('podBoxComponent', () => { + it('should render three boxes', () => { + const count = 3; + const vm = createComponent(count); + const rects = vm.$el.querySelectorAll('rect'); + + expect(rects.length).toEqual(3); + expect(parseInt(rects[2].getAttribute('x'), 10)).toEqual(40); + + vm.$destroy(); + }); +}); diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/frontend/serverless/components/url_spec.js index 21a879a49bb..d05a9bba103 100644 --- a/spec/javascripts/serverless/components/url_spec.js +++ b/spec/frontend/serverless/components/url_spec.js @@ -1,15 +1,14 @@ import Vue from 'vue'; - import urlComponent from '~/serverless/components/url.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = uri => { - const component = Vue.extend(urlComponent); +import { shallowMount } from '@vue/test-utils'; - return mountComponent(component, { - uri, - }); -}; +const createComponent = uri => + shallowMount(Vue.extend(urlComponent), { + propsData: { + uri, + }, + sync: false, + }).vm; describe('urlComponent', () => { it('should render correctly', () => { @@ -17,9 +16,7 @@ describe('urlComponent', () => { const vm = createComponent(uri); expect(vm.$el.classList.contains('clipboard-group')).toBe(true); - expect(vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text')).toEqual( - uri, - ); + expect(vm.$el.querySelector('clipboardbutton-stub').getAttribute('text')).toEqual(uri); expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); diff --git a/spec/javascripts/serverless/mock_data.js b/spec/frontend/serverless/mock_data.js index ecd393b174c..a2c18616324 100644 --- a/spec/javascripts/serverless/mock_data.js +++ b/spec/frontend/serverless/mock_data.js @@ -77,3 +77,60 @@ export const mockMultilineServerlessFunction = { description: 'testfunc1\nA test service line\\nWith additional services', image: 'knative-test-container-buildtemplate', }; + +export const mockMetrics = { + success: true, + last_update: '2019-02-28T19:11:38.926Z', + metrics: { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [[1551352298.756, '0'], [1551352358.756, '0']], + }, + ], + }, + ], + }, +}; + +export const mockNormalizedMetrics = { + id: 22, + title: 'Knative function invocations', + required_metrics: ['container_memory_usage_bytes', 'container_cpu_usage_seconds_total'], + weight: 0, + y_label: 'Invocations', + queries: [ + { + query_range: + 'floor(sum(rate(istio_revision_request_count{destination_configuration="%{function_name}", destination_namespace="%{kube_namespace}"}[1m])*30))', + unit: 'requests', + label: 'invocations / minute', + result: [ + { + metric: {}, + values: [ + { + time: '2019-02-28T11:11:38.756Z', + value: 0, + }, + { + time: '2019-02-28T11:12:38.756Z', + value: 0, + }, + ], + }, + ], + }, + ], +}; diff --git a/spec/frontend/serverless/store/actions_spec.js b/spec/frontend/serverless/store/actions_spec.js new file mode 100644 index 00000000000..aac57c75a4f --- /dev/null +++ b/spec/frontend/serverless/store/actions_spec.js @@ -0,0 +1,90 @@ +import MockAdapter from 'axios-mock-adapter'; +import statusCodes from '~/lib/utils/http_status'; +import { fetchFunctions, fetchMetrics } from '~/serverless/store/actions'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; +import axios from '~/lib/utils/axios_utils'; +import testAction from '../../helpers/vuex_action_helper'; +import { adjustMetricQuery } from '../utils'; + +describe('ServerlessActions', () => { + describe('fetchFunctions', () => { + it('should successfully fetch functions', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockServerlessFunctions)); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [ + { type: 'requestFunctionsLoading' }, + { type: 'receiveFunctionsSuccess', payload: mockServerlessFunctions }, + ], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully retry', done => { + const endpoint = '/functions'; + const mock = new MockAdapter(axios); + mock + .onGet(endpoint) + .reply(() => new Promise(resolve => setTimeout(() => resolve(200), Infinity))); + + testAction( + fetchFunctions, + { functionsPath: endpoint }, + {}, + [], + [{ type: 'requestFunctionsLoading' }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('fetchMetrics', () => { + it('should return no prometheus', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.NO_CONTENT); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: false }, + {}, + [], + [{ type: 'receiveMetricsNoPrometheus' }], + () => { + mock.restore(); + done(); + }, + ); + }); + + it('should successfully fetch metrics', done => { + const endpoint = '/metrics'; + const mock = new MockAdapter(axios); + mock.onGet(endpoint).reply(statusCodes.OK, JSON.stringify(mockMetrics)); + + testAction( + fetchMetrics, + { metricsPath: endpoint, hasPrometheus: true }, + {}, + [], + [{ type: 'receiveMetricsSuccess', payload: adjustMetricQuery(mockMetrics) }], + () => { + mock.restore(); + done(); + }, + ); + }); + }); +}); diff --git a/spec/frontend/serverless/store/getters_spec.js b/spec/frontend/serverless/store/getters_spec.js new file mode 100644 index 00000000000..fb549c8f153 --- /dev/null +++ b/spec/frontend/serverless/store/getters_spec.js @@ -0,0 +1,43 @@ +import serverlessState from '~/serverless/store/state'; +import * as getters from '~/serverless/store/getters'; +import { mockServerlessFunctions } from '../mock_data'; + +describe('Serverless Store Getters', () => { + let state; + + beforeEach(() => { + state = serverlessState; + }); + + describe('hasPrometheusMissingData', () => { + it('should return false if Prometheus is not installed', () => { + state.hasPrometheus = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return false if Prometheus is installed and there is data', () => { + state.hasPrometheusData = true; + + expect(getters.hasPrometheusMissingData(state)).toEqual(false); + }); + + it('should return true if Prometheus is installed and there is no data', () => { + state.hasPrometheus = true; + state.hasPrometheusData = false; + + expect(getters.hasPrometheusMissingData(state)).toEqual(true); + }); + }); + + describe('getFunctions', () => { + it('should translate the raw function array to group the functions per environment scope', () => { + state.functions = mockServerlessFunctions; + + const funcs = getters.getFunctions(state); + + expect(Object.keys(funcs)).toContain('*'); + expect(funcs['*'].length).toEqual(2); + }); + }); +}); diff --git a/spec/frontend/serverless/store/mutations_spec.js b/spec/frontend/serverless/store/mutations_spec.js new file mode 100644 index 00000000000..ca3053e5c38 --- /dev/null +++ b/spec/frontend/serverless/store/mutations_spec.js @@ -0,0 +1,86 @@ +import mutations from '~/serverless/store/mutations'; +import * as types from '~/serverless/store/mutation_types'; +import { mockServerlessFunctions, mockMetrics } from '../mock_data'; + +describe('ServerlessMutations', () => { + describe('Functions List Mutations', () => { + it('should ensure loading is true', () => { + const state = {}; + + mutations[types.REQUEST_FUNCTIONS_LOADING](state); + + expect(state.isLoading).toEqual(true); + }); + + it('should set proper state once functions are loaded', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_SUCCESS](state, mockServerlessFunctions); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(true); + expect(state.functions).toEqual(mockServerlessFunctions); + }); + + it('should ensure loading has stopped and hasFunctionData is false when there are no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + }); + + it('should ensure loading has stopped, and an error is raised', () => { + const state = {}; + + mutations[types.RECEIVE_FUNCTIONS_ERROR](state, 'sample error'); + + expect(state.isLoading).toEqual(false); + expect(state.hasFunctionData).toEqual(false); + expect(state.functions).toBe(undefined); + expect(state.error).not.toBe(undefined); + }); + }); + + describe('Function Details Metrics Mutations', () => { + it('should ensure isLoading and hasPrometheus data flags indicate data is loaded', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_SUCCESS](state, mockMetrics); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(true); + expect(state.graphData).toEqual(mockMetrics); + }); + + it('should ensure isLoading and hasPrometheus data flags are cleared indicating no functions available', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NODATA_SUCCESS](state); + + expect(state.isLoading).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + expect(state.graphData).toBe(undefined); + }); + + it('should properly indicate an error', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_ERROR](state, 'sample error'); + + expect(state.hasPrometheusData).toEqual(false); + expect(state.error).not.toBe(undefined); + }); + + it('should properly indicate when prometheus is installed', () => { + const state = {}; + + mutations[types.RECEIVE_METRICS_NO_PROMETHEUS](state); + + expect(state.hasPrometheus).toEqual(false); + expect(state.hasPrometheusData).toEqual(false); + }); + }); +}); diff --git a/spec/frontend/serverless/utils.js b/spec/frontend/serverless/utils.js new file mode 100644 index 00000000000..5ce2e37d493 --- /dev/null +++ b/spec/frontend/serverless/utils.js @@ -0,0 +1,20 @@ +export const adjustMetricQuery = data => { + const updatedMetric = data.metrics; + + const queries = data.metrics.queries.map(query => ({ + ...query, + result: query.result.map(result => ({ + ...result, + values: result.values.map(([timestamp, value]) => ({ + time: new Date(timestamp * 1000).toISOString(), + value: Number(value), + })), + })), + })); + + updatedMetric.queries = queries; + return updatedMetric; +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap new file mode 100644 index 00000000000..add0c36a120 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/resizable_chart_container_spec.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Resizable Chart Container renders the component 1`] = ` +<div> + <div + class="slot" + > + <span + class="width" + > + 0 + </span> + + <span + class="height" + > + 0 + </span> + </div> +</div> +`; diff --git a/spec/frontend/vue_shared/components/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js new file mode 100644 index 00000000000..8f533e8ab24 --- /dev/null +++ b/spec/frontend/vue_shared/components/resizable_chart_container_spec.js @@ -0,0 +1,64 @@ +import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue'; +import $ from 'jquery'; + +jest.mock('~/lib/utils/common_utils', () => ({ + debounceByAnimationFrame(callback) { + return jest.spyOn({ callback }, 'callback'); + }, +})); + +describe('Resizable Chart Container', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(ResizableChartContainer, { + attachToDocument: true, + scopedSlots: { + default: ` + <div class="slot" slot-scope="{ width, height }"> + <span class="width">{{width}}</span> + <span class="height">{{height}}</span> + </div> + `, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the component', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('updates the slot width and height props', () => { + const width = 1920; + const height = 1080; + + // JSDOM mocks and sets clientWidth/clientHeight to 0 so we set manually + wrapper.vm.$refs.chartWrapper = { clientWidth: width, clientHeight: height }; + + $(document).trigger('content.resize'); + + return Vue.nextTick().then(() => { + const widthNode = wrapper.find('.slot > .width'); + const heightNode = wrapper.find('.slot > .height'); + + expect(parseInt(widthNode.text(), 10)).toEqual(width); + expect(parseInt(heightNode.text(), 10)).toEqual(height); + }); + }); + + it('calls onResize on manual resize', () => { + $(document).trigger('content.resize'); + expect(wrapper.vm.debouncedResize).toHaveBeenCalled(); + }); + + it('calls onResize on page resize', () => { + window.dispatchEvent(new Event('resize')); + expect(wrapper.vm.debouncedResize).toHaveBeenCalled(); + }); +}); diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index 4b40d523287..37e9ddadb8c 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -59,19 +59,19 @@ describe IconsHelper do describe 'non existing icon' do non_existing = 'non_existing_icon_sprite' - it 'should raise in development mode' do + it 'raises in development mode' do allow(Rails.env).to receive(:development?).and_return(true) expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) end - it 'should raise in test mode' do + it 'raises in test mode' do allow(Rails.env).to receive(:test?).and_return(true) expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/) end - it 'should not raise in production mode' do + it 'does not raise in production mode' do allow(Rails.env).to receive(:test?).and_return(false) allow(Rails.env).to receive(:development?).and_return(false) diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 012678db9c2..a049b5a6133 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -249,4 +249,24 @@ describe LabelsHelper do .to match_array([label2, label4, label1, label3]) end end + + describe 'label_from_hash' do + it 'builds a group label with whitelisted attributes' do + label = label_from_hash({ title: 'foo', color: 'bar', id: 1, group_id: 1 }) + + expect(label).to be_a(GroupLabel) + expect(label.id).to be_nil + expect(label.title).to eq('foo') + expect(label.color).to eq('bar') + end + + it 'builds a project label with whitelisted attributes' do + label = label_from_hash({ title: 'foo', color: 'bar', id: 1, project_id: 1 }) + + expect(label).to be_a(ProjectLabel) + expect(label.id).to be_nil + expect(label.title).to eq('foo') + expect(label.color).to eq('bar') + end + end end diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 7ccbdcd1332..601f864ef36 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -1,10 +1,38 @@ require 'spec_helper' -describe NamespacesHelper do +describe NamespacesHelper, :postgresql do let!(:admin) { create(:admin) } - let!(:admin_group) { create(:group, :private) } + let!(:admin_project_creation_level) { nil } + let!(:admin_group) do + create(:group, + :private, + project_creation_level: admin_project_creation_level) + end let!(:user) { create(:user) } - let!(:user_group) { create(:group, :private) } + let!(:user_project_creation_level) { nil } + let!(:user_group) do + create(:group, + :private, + project_creation_level: user_project_creation_level) + end + let!(:subgroup1) do + create(:group, + :private, + parent: admin_group, + project_creation_level: nil) + end + let!(:subgroup2) do + create(:group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + end + let!(:subgroup3) do + create(:group, + :private, + parent: admin_group, + project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + end before do admin_group.add_owner(admin) @@ -105,5 +133,43 @@ describe NamespacesHelper do helper.namespaces_options end end + + describe 'include_groups_with_developer_maintainer_access parameter' do + context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set for a project' do + let!(:admin_project_creation_level) { ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS } + + it 'returns groups where user is a developer' do + allow(helper).to receive(:current_user).and_return(user) + stub_application_setting(default_project_creation: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) + admin_group.add_user(user, GroupMember::DEVELOPER) + + options = helper.namespaces_options_with_developer_maintainer_access + + expect(options).to include(admin_group.name) + expect(options).not_to include(subgroup1.name) + expect(options).to include(subgroup2.name) + expect(options).not_to include(subgroup3.name) + expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + end + + context 'when DEVELOPER_MAINTAINER_PROJECT_ACCESS is set globally' do + it 'return groups where default is not overridden' do + allow(helper).to receive(:current_user).and_return(user) + stub_application_setting(default_project_creation: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) + admin_group.add_user(user, GroupMember::DEVELOPER) + + options = helper.namespaces_options_with_developer_maintainer_access + + expect(options).to include(admin_group.name) + expect(options).to include(subgroup1.name) + expect(options).to include(subgroup2.name) + expect(options).not_to include(subgroup3.name) + expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + end + end end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 9cff0291250..2f59cfda0a0 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -12,7 +12,7 @@ describe SearchHelper do allow(self).to receive(:current_user).and_return(nil) end - it "it returns nil" do + it "returns nil" do expect(search_autocomplete_opts("q")).to be_nil end end diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index bfec7ad4bba..e384e2bf9a0 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe VersionCheckHelper do describe '#version_status_badge' do - it 'should return nil if not dev environment and not enabled' do + it 'returns nil if not dev environment and not enabled' do allow(Rails.env).to receive(:production?) { false } allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false } @@ -16,16 +16,16 @@ describe VersionCheckHelper do allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } end - it 'should return an image tag' do + it 'returns an image tag' do expect(helper.version_status_badge).to start_with('<img') end - it 'should have a js prefixed css class' do + it 'has a js prefixed css class' do expect(helper.version_status_badge) .to match(/class="js-version-status-badge lazy"/) end - it 'should have a VersionCheck url as the src' do + it 'has a VersionCheck url as the src' do expect(helper.version_status_badge) .to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"}) end diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 54fb0e8228b..e4ff3eb381f 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -178,6 +178,7 @@ describe('Issue model', () => { spyOn(Vue.http, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([1]); done(); + return Promise.resolve(); }); issue.update('url'); @@ -187,6 +188,7 @@ describe('Issue model', () => { spyOn(Vue.http, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([0]); done(); + return Promise.resolve(); }); issue.removeAllAssignees(); diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 8d7c52a2876..3ce69bc3c20 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -57,6 +57,24 @@ describe('diffs/components/app', () => { wrapper.destroy(); }); + it('adds container-limiting classes when showFileTree is false with inline diffs', () => { + createComponent({}, ({ state }) => { + state.diffs.showTreeList = false; + state.diffs.isParallelView = false; + }); + + expect(wrapper.contains('.container-limited.limit-container-width')).toBe(true); + }); + + it('does not add container-limiting classes when showFileTree is false with inline diffs', () => { + createComponent({}, ({ state }) => { + state.diffs.showTreeList = true; + state.diffs.isParallelView = false; + }); + + expect(wrapper.contains('.container-limited.limit-container-width')).toBe(false); + }); + it('displays loading icon on loading', () => { createComponent({}, ({ state }) => { state.diffs.isLoading = true; diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js index e886f962d2f..77f8352047c 100644 --- a/spec/javascripts/diffs/components/compare_versions_spec.js +++ b/spec/javascripts/diffs/components/compare_versions_spec.js @@ -66,6 +66,26 @@ describe('CompareVersions', () => { expect(inlineBtn.innerHTML).toContain('Inline'); expect(parallelBtn.innerHTML).toContain('Side-by-side'); }); + + it('adds container-limiting classes when showFileTree is false with inline diffs', () => { + vm.isLimitedContainer = true; + + vm.$nextTick(() => { + const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width'); + + expect(limitedContainer).not.toBeNull(); + }); + }); + + it('does not add container-limiting classes when showFileTree is false with inline diffs', () => { + vm.isLimitedContainer = false; + + vm.$nextTick(() => { + const limitedContainer = vm.$el.querySelector('.container-limited.limit-container-width'); + + expect(limitedContainer).toBeNull(); + }); + }); }); describe('setInlineDiffViewType', () => { diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 6614069f44d..e1170c9762e 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -672,7 +672,7 @@ describe('diff_file_header', () => { vm = mountComponentWithStore(Component, { props, store }); - expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show changes only'); + expect(vm.$el.querySelector('.ic-doc-changes')).not.toBeNull(); }); it('shows expand text', () => { @@ -680,7 +680,7 @@ describe('diff_file_header', () => { vm = mountComponentWithStore(Component, { props, store }); - expect(vm.$el.querySelector('.js-expand-file').textContent).toContain('Show full file'); + expect(vm.$el.querySelector('.ic-doc-expand')).not.toBeNull(); }); it('renders loading icon', () => { diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 645b3aa788a..0f3f9a10f94 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -65,3 +65,61 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller store_frontend_fixture(response, fixture_file_name) end end + +describe API::Issues, '(JavaScript fixtures)', type: :request do + include ApiHelpers + include JavaScriptFixturesHelpers + + def get_related_merge_requests(project_id, issue_iid, user = nil) + get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user) + end + + def create_referencing_mr(user, project, issue) + attributes = { + author: user, + source_project: project, + target_project: project, + source_branch: "master", + target_branch: "test", + assignee: user, + description: "See #{issue.to_reference}" + } + create(:merge_request, attributes).tap do |merge_request| + create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true)) + end + end + + it 'issues/related_merge_requests.json' do |example| + user = create(:user) + project = create(:project, :public, creator_id: user.id, namespace: user.namespace) + issue_title = 'foo' + issue_description = 'closed' + milestone = create(:milestone, title: '1.0.0', project: project) + issue = create :issue, + author: user, + assignees: [user], + project: project, + milestone: milestone, + created_at: generate(:past_time), + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description + + project.add_reporter(user) + create_referencing_mr(user, project, issue) + + create(:merge_request, + :simple, + author: user, + source_project: project, + target_project: project, + description: "Some description") + project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace) + create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline)) + + get_related_merge_requests(project.id, issue.iid, user) + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js index 7deed985219..f00bc2eeb6d 100644 --- a/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js +++ b/spec/javascripts/frequent_items/components/frequent_items_list_item_spec.js @@ -1,25 +1,31 @@ import Vue from 'vue'; import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; +import { trimText } from 'spec/helpers/vue_component_helper'; import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here const createComponent = () => { const Component = Vue.extend(frequentItemsListItemComponent); - return mountComponent(Component, { - itemId: mockProject.id, - itemName: mockProject.name, - namespace: mockProject.namespace, - webUrl: mockProject.webUrl, - avatarUrl: mockProject.avatarUrl, + return shallowMount(Component, { + propsData: { + itemId: mockProject.id, + itemName: mockProject.name, + namespace: mockProject.namespace, + webUrl: mockProject.webUrl, + avatarUrl: mockProject.avatarUrl, + }, }); }; describe('FrequentItemsListItemComponent', () => { + let wrapper; let vm; beforeEach(() => { - vm = createComponent(); + wrapper = createComponent(); + + ({ vm } = wrapper); }); afterEach(() => { @@ -29,11 +35,11 @@ describe('FrequentItemsListItemComponent', () => { describe('computed', () => { describe('hasAvatar', () => { it('should return `true` or `false` if whether avatar is present or not', () => { - vm.avatarUrl = 'path/to/avatar.png'; + wrapper.setProps({ avatarUrl: 'path/to/avatar.png' }); expect(vm.hasAvatar).toBe(true); - vm.avatarUrl = null; + wrapper.setProps({ avatarUrl: null }); expect(vm.hasAvatar).toBe(false); }); @@ -41,41 +47,49 @@ describe('FrequentItemsListItemComponent', () => { describe('highlightedItemName', () => { it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => { - vm.matcher = 'lab'; + wrapper.setProps({ matcher: 'lab' }); - expect(vm.highlightedItemName).toContain('<b>Lab</b>'); + expect(wrapper.find('.js-frequent-items-item-title').html()).toContain( + '<b>L</b><b>a</b><b>b</b>', + ); }); it('should return project name as it is if `matcher` is not available', () => { - vm.matcher = null; + wrapper.setProps({ matcher: null }); - expect(vm.highlightedItemName).toBe(mockProject.name); + expect(trimText(wrapper.find('.js-frequent-items-item-title').text())).toBe( + mockProject.name, + ); }); }); describe('truncatedNamespace', () => { it('should truncate project name from namespace string', () => { - vm.namespace = 'platform / nokia-3310'; + wrapper.setProps({ namespace: 'platform / nokia-3310' }); - expect(vm.truncatedNamespace).toBe('platform'); + expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe('platform'); }); it('should truncate namespace string from the middle if it includes more than two groups in path', () => { - vm.namespace = 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310'; + wrapper.setProps({ + namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310', + }); - expect(vm.truncatedNamespace).toBe('platform / ... / Mobile Chipset'); + expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe( + 'platform / ... / Mobile Chipset', + ); }); }); }); describe('template', () => { it('should render component element', () => { - expect(vm.$el.classList.contains('frequent-items-list-item-container')).toBeTruthy(); - expect(vm.$el.querySelectorAll('a').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-avatar-container').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-metadata-container').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-title').length).toBe(1); - expect(vm.$el.querySelectorAll('.frequent-items-item-namespace').length).toBe(1); + expect(wrapper.classes()).toContain('frequent-items-list-item-container'); + expect(wrapper.findAll('a').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-avatar-container').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-metadata-container').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-title').length).toBe(1); + expect(wrapper.findAll('.frequent-items-item-namespace').length).toBe(1); }); }); }); diff --git a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js index d564292f1ba..ddbbc5c2d29 100644 --- a/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js +++ b/spec/javascripts/frequent_items/components/frequent_items_search_input_spec.js @@ -1,19 +1,22 @@ import Vue from 'vue'; import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue'; import eventHub from '~/frequent_items/event_hub'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { shallowMount } from '@vue/test-utils'; const createComponent = (namespace = 'projects') => { const Component = Vue.extend(searchComponent); - return mountComponent(Component, { namespace }); + return shallowMount(Component, { propsData: { namespace } }); }; describe('FrequentItemsSearchInputComponent', () => { + let wrapper; let vm; beforeEach(() => { - vm = createComponent(); + wrapper = createComponent(); + + ({ vm } = wrapper); }); afterEach(() => { @@ -35,7 +38,7 @@ describe('FrequentItemsSearchInputComponent', () => { describe('mounted', () => { it('should listen `dropdownOpen` event', done => { spyOn(eventHub, '$on'); - const vmX = createComponent(); + const vmX = createComponent().vm; Vue.nextTick(() => { expect(eventHub.$on).toHaveBeenCalledWith( @@ -49,7 +52,7 @@ describe('FrequentItemsSearchInputComponent', () => { describe('beforeDestroy', () => { it('should unbind event listeners on eventHub', done => { - const vmX = createComponent(); + const vmX = createComponent().vm; spyOn(eventHub, '$off'); vmX.$mount(); @@ -67,12 +70,12 @@ describe('FrequentItemsSearchInputComponent', () => { describe('template', () => { it('should render component element', () => { - const inputEl = vm.$el.querySelector('input.form-control'); - - expect(vm.$el.classList.contains('search-input-container')).toBeTruthy(); - expect(inputEl).not.toBe(null); - expect(inputEl.getAttribute('placeholder')).toBe('Search your projects'); - expect(vm.$el.querySelector('.search-icon')).toBeDefined(); + expect(wrapper.classes()).toContain('search-input-container'); + expect(wrapper.contains('input.form-control')).toBe(true); + expect(wrapper.contains('.search-icon')).toBe(true); + expect(wrapper.find('input.form-control').attributes('placeholder')).toBe( + 'Search your projects', + ); }); }); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js index 3a5d6c8a90b..23e6a055518 100644 --- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import store from '~/ide/stores'; +import consts from '~/ide/stores/modules/commit/constants'; import commitActions from '~/ide/components/commit_sidebar/actions.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from 'spec/ide/helpers'; @@ -7,20 +8,33 @@ import { projectData } from 'spec/ide/mock_data'; describe('IDE commit sidebar actions', () => { let vm; - - beforeEach(done => { + const createComponent = ({ + hasMR = false, + commitAction = consts.COMMIT_TO_NEW_BRANCH, + mergeRequestsEnabled = true, + currentBranchId = 'master', + shouldCreateMR = false, + } = {}) => { const Component = Vue.extend(commitActions); vm = createComponentWithStore(Component, store); - vm.$store.state.currentBranchId = 'master'; + vm.$store.state.currentBranchId = currentBranchId; vm.$store.state.currentProjectId = 'abcproject'; + vm.$store.state.commit.commitAction = commitAction; Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData }); + vm.$store.state.projects.abcproject.merge_requests_enabled = mergeRequestsEnabled; + vm.$store.state.commit.shouldCreateMR = shouldCreateMR; - vm.$mount(); + if (hasMR) { + vm.$store.state.currentMergeRequestId = '1'; + vm.$store.state.projects[store.state.currentProjectId].mergeRequests[ + store.state.currentMergeRequestId + ] = { foo: 'bar' }; + } - Vue.nextTick(done); - }); + return vm.$mount(); + }; afterEach(() => { vm.$destroy(); @@ -28,16 +42,20 @@ describe('IDE commit sidebar actions', () => { resetStore(vm.$store); }); - it('renders 3 groups', () => { - expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(3); + it('renders 2 groups', () => { + createComponent(); + + expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2); }); it('renders current branch text', () => { + createComponent(); + expect(vm.$el.textContent).toContain('Commit to master branch'); }); it('hides merge request option when project merge requests are disabled', done => { - vm.$store.state.projects.abcproject.merge_requests_enabled = false; + createComponent({ mergeRequestsEnabled: false }); vm.$nextTick(() => { expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(2); @@ -49,9 +67,53 @@ describe('IDE commit sidebar actions', () => { describe('commitToCurrentBranchText', () => { it('escapes current branch', () => { - vm.$store.state.currentBranchId = '<img src="x" />'; + const injectedSrc = '<img src="x" />'; + createComponent({ currentBranchId: injectedSrc }); + + expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc); + }); + }); + + describe('create new MR checkbox', () => { + it('disables `createMR` button when an MR already exists and committing to current branch', () => { + createComponent({ hasMR: true, commitAction: consts.COMMIT_TO_CURRENT_BRANCH }); + + expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(true); + }); + + it('does not disable checkbox if MR does not exist', () => { + createComponent({ hasMR: false }); + + expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false); + }); + + it('does not disable checkbox when creating a new branch', () => { + createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH }); + + expect(vm.$el.querySelector('input[type="checkbox"]').disabled).toBe(false); + }); + + it('toggles off new MR when switching back to commit to current branch and MR exists', () => { + createComponent({ + commitAction: consts.COMMIT_TO_NEW_BRANCH, + shouldCreateMR: true, + }); + const currentBranchRadio = vm.$el.querySelector( + `input[value="${consts.COMMIT_TO_CURRENT_BRANCH}"`, + ); + currentBranchRadio.click(); + + vm.$nextTick(() => { + expect(vm.$store.state.commit.shouldCreateMR).toBe(false); + }); + }); + + it('toggles `shouldCreateMR` when clicking checkbox', () => { + createComponent(); + const el = vm.$el.querySelector('input[type="checkbox"]'); + el.dispatchEvent(new Event('change')); - expect(vm.commitToCurrentBranchText).not.toContain('<img src="x" />'); + expect(vm.$store.state.commit.shouldCreateMR).toBe(true); }); }); }); diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js index c93a939ad71..d7fed3f0681 100644 --- a/spec/javascripts/ide/components/file_row_extra_spec.js +++ b/spec/javascripts/ide/components/file_row_extra_spec.js @@ -20,7 +20,7 @@ describe('IDE extra file row component', () => { file: { ...file('test'), }, - mouseOver: false, + dropdownOpen: false, }); spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount); diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js index 83e530f0a6a..aaebe88f314 100644 --- a/spec/javascripts/ide/components/new_dropdown/index_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js @@ -56,11 +56,11 @@ describe('new dropdown component', () => { }); }); - describe('dropdownOpen', () => { + describe('isOpen', () => { it('scrolls dropdown into view', done => { spyOn(vm.$refs.dropdownMenu, 'scrollIntoView'); - vm.dropdownOpen = true; + vm.isOpen = true; setTimeout(() => { expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({ diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index a5839630657..4dd0c1150eb 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -2,7 +2,6 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import actions, { - getMergeRequestsForBranch, getMergeRequestData, getMergeRequestChanges, getMergeRequestVersions, @@ -12,13 +11,17 @@ import service from '~/ide/services'; import { activityBarViews } from '~/ide/constants'; import { resetStore } from '../../helpers'; +const TEST_PROJECT = 'abcproject'; +const TEST_PROJECT_ID = 17; + describe('IDE store merge request actions', () => { let mock; beforeEach(() => { mock = new MockAdapter(axios); - store.state.projects.abcproject = { + store.state.projects[TEST_PROJECT] = { + id: TEST_PROJECT_ID, mergeRequests: {}, }; }); @@ -41,10 +44,11 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequests service method', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { - expect(service.getProjectMergeRequests).toHaveBeenCalledWith('abcproject', { + expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, { source_branch: 'bar', + source_project_id: TEST_PROJECT_ID, order_by: 'created_at', per_page: 1, }); @@ -56,13 +60,11 @@ describe('IDE store merge request actions', () => { it('sets the "Merge Request" Object', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { - expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(1); - expect(Object.keys(store.state.projects.abcproject.mergeRequests)[0]).toEqual('2'); - expect(store.state.projects.abcproject.mergeRequests[2]).toEqual( - jasmine.objectContaining(mrData), - ); + expect(store.state.projects.abcproject.mergeRequests).toEqual({ + '2': jasmine.objectContaining(mrData), + }); done(); }) .catch(done.fail); @@ -70,7 +72,7 @@ describe('IDE store merge request actions', () => { it('sets "Current Merge Request" object to the most recent MR', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { expect(store.state.currentMergeRequestId).toEqual('2'); done(); @@ -87,9 +89,9 @@ describe('IDE store merge request actions', () => { it('does not fail if there are no merge requests for current branch', done => { store - .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'foo' }) + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'foo' }) .then(() => { - expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(0); + expect(store.state.projects[TEST_PROJECT].mergeRequests).toEqual({}); expect(store.state.currentMergeRequestId).toEqual(''); done(); }) @@ -106,7 +108,8 @@ describe('IDE store merge request actions', () => { it('flashes message, if error', done => { const flashSpy = spyOnDependency(actions, 'flash'); - getMergeRequestsForBranch({ commit() {} }, { projectId: 'abcproject', branchId: 'bar' }) + store + .dispatch('getMergeRequestsForBranch', { projectId: TEST_PROJECT, branchId: 'bar' }) .then(() => { fail('Expected getMergeRequestsForBranch to throw an error'); }) @@ -132,9 +135,9 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequestData service method', done => { store - .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestData).toHaveBeenCalledWith('abcproject', 1, { + expect(service.getProjectMergeRequestData).toHaveBeenCalledWith(TEST_PROJECT, 1, { render_html: true, }); @@ -145,10 +148,12 @@ describe('IDE store merge request actions', () => { it('sets the Merge Request Object', done => { store - .dispatch('getMergeRequestData', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestData', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(store.state.projects.abcproject.mergeRequests['1'].title).toBe('mergerequest'); expect(store.state.currentMergeRequestId).toBe(1); + expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].title).toBe( + 'mergerequest', + ); done(); }) @@ -170,7 +175,7 @@ describe('IDE store merge request actions', () => { dispatch, state: store.state, }, - { projectId: 'abcproject', mergeRequestId: 1 }, + { projectId: TEST_PROJECT, mergeRequestId: 1 }, ) .then(done.fail) .catch(() => { @@ -179,7 +184,7 @@ describe('IDE store merge request actions', () => { action: jasmine.any(Function), actionText: 'Please try again', actionPayload: { - projectId: 'abcproject', + projectId: TEST_PROJECT, mergeRequestId: 1, force: false, }, @@ -193,7 +198,7 @@ describe('IDE store merge request actions', () => { describe('getMergeRequestChanges', () => { beforeEach(() => { - store.state.projects.abcproject.mergeRequests['1'] = { changes: [] }; + store.state.projects[TEST_PROJECT].mergeRequests['1'] = { changes: [] }; }); describe('success', () => { @@ -207,9 +212,9 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequestChanges service method', done => { store - .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith('abcproject', 1); + expect(service.getProjectMergeRequestChanges).toHaveBeenCalledWith(TEST_PROJECT, 1); done(); }) @@ -218,9 +223,9 @@ describe('IDE store merge request actions', () => { it('sets the Merge Request Changes Object', done => { store - .dispatch('getMergeRequestChanges', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestChanges', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(store.state.projects.abcproject.mergeRequests['1'].changes.title).toBe( + expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].changes.title).toBe( 'mergerequest', ); done(); @@ -243,7 +248,7 @@ describe('IDE store merge request actions', () => { dispatch, state: store.state, }, - { projectId: 'abcproject', mergeRequestId: 1 }, + { projectId: TEST_PROJECT, mergeRequestId: 1 }, ) .then(done.fail) .catch(() => { @@ -252,7 +257,7 @@ describe('IDE store merge request actions', () => { action: jasmine.any(Function), actionText: 'Please try again', actionPayload: { - projectId: 'abcproject', + projectId: TEST_PROJECT, mergeRequestId: 1, force: false, }, @@ -266,7 +271,7 @@ describe('IDE store merge request actions', () => { describe('getMergeRequestVersions', () => { beforeEach(() => { - store.state.projects.abcproject.mergeRequests['1'] = { versions: [] }; + store.state.projects[TEST_PROJECT].mergeRequests['1'] = { versions: [] }; }); describe('success', () => { @@ -279,9 +284,9 @@ describe('IDE store merge request actions', () => { it('calls getProjectMergeRequestVersions service method', done => { store - .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith('abcproject', 1); + expect(service.getProjectMergeRequestVersions).toHaveBeenCalledWith(TEST_PROJECT, 1); done(); }) @@ -290,9 +295,9 @@ describe('IDE store merge request actions', () => { it('sets the Merge Request Versions Object', done => { store - .dispatch('getMergeRequestVersions', { projectId: 'abcproject', mergeRequestId: 1 }) + .dispatch('getMergeRequestVersions', { projectId: TEST_PROJECT, mergeRequestId: 1 }) .then(() => { - expect(store.state.projects.abcproject.mergeRequests['1'].versions.length).toBe(1); + expect(store.state.projects[TEST_PROJECT].mergeRequests['1'].versions.length).toBe(1); done(); }) .catch(done.fail); @@ -313,7 +318,7 @@ describe('IDE store merge request actions', () => { dispatch, state: store.state, }, - { projectId: 'abcproject', mergeRequestId: 1 }, + { projectId: TEST_PROJECT, mergeRequestId: 1 }, ) .then(done.fail) .catch(() => { @@ -322,7 +327,7 @@ describe('IDE store merge request actions', () => { action: jasmine.any(Function), actionText: 'Please try again', actionPayload: { - projectId: 'abcproject', + projectId: TEST_PROJECT, mergeRequestId: 1, force: false, }, @@ -336,7 +341,7 @@ describe('IDE store merge request actions', () => { describe('openMergeRequest', () => { const mr = { - projectId: 'abcproject', + projectId: TEST_PROJECT, targetProjectId: 'defproject', mergeRequestId: 2, }; diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 7b0963713fb..cd519eaed7c 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -279,5 +279,22 @@ describe('IDE store project actions', () => { .then(done) .catch(done.fail); }); + + it('creates a new file supplied via URL if the file does not exist yet', done => { + openBranch(store, { ...branch, basePath: 'not-existent.md' }) + .then(() => { + expect(store.dispatch).not.toHaveBeenCalledWith( + 'handleTreeEntryAction', + jasmine.anything(), + ); + + expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', { + name: 'not-existent.md', + type: 'blob', + }); + }) + .then(done) + .catch(done.fail); + }); }); }); diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js index fbb676aab33..5ed9b9003a7 100644 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ b/spec/javascripts/ide/stores/actions/tree_spec.js @@ -1,6 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'spec/helpers/vuex_action_helper'; -import { showTreeEntry, getFiles } from '~/ide/stores/actions/tree'; +import { showTreeEntry, getFiles, setDirectoryData } from '~/ide/stores/actions/tree'; import * as types from '~/ide/stores/mutation_types'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; @@ -206,4 +206,35 @@ describe('Multi-file store tree actions', () => { ); }); }); + + describe('setDirectoryData', () => { + it('sets tree correctly if there are no opened files yet', done => { + const treeFile = file({ name: 'README.md' }); + store.state.trees['abcproject/master'] = {}; + + testAction( + setDirectoryData, + { projectId: 'abcproject', branchId: 'master', treeList: [treeFile] }, + store.state, + [ + { + type: types.SET_DIRECTORY_DATA, + payload: { + treePath: 'abcproject/master', + data: [treeFile], + }, + }, + { + type: types.TOGGLE_LOADING, + payload: { + entry: {}, + forceValue: false, + }, + }, + ], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index 34d97347438..abc97f3072c 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -3,7 +3,7 @@ import store from '~/ide/stores'; import service from '~/ide/services'; import router from '~/ide/ide_router'; import eventHub from '~/ide/eventhub'; -import * as consts from '~/ide/stores/modules/commit/constants'; +import consts from '~/ide/stores/modules/commit/constants'; import { resetStore, file } from 'spec/ide/helpers'; describe('IDE commit module actions', () => { @@ -389,7 +389,8 @@ describe('IDE commit module actions', () => { it('redirects to new merge request page', done => { spyOn(eventHub, '$on'); - store.state.commit.commitAction = '3'; + store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; + store.state.commit.shouldCreateMR = true; store .dispatch('commit/commitChanges') @@ -405,6 +406,21 @@ describe('IDE commit module actions', () => { .catch(done.fail); }); + it('does not redirect to new merge request page when shouldCreateMR is not checked', done => { + spyOn(eventHub, '$on'); + + store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH; + store.state.commit.shouldCreateMR = false; + + store + .dispatch('commit/commitChanges') + .then(() => { + expect(visitUrl).not.toHaveBeenCalled(); + done(); + }) + .catch(done.fail); + }); + it('resets changed files before redirecting', done => { spyOn(eventHub, '$on'); diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js index 702e78ef773..e00fd7199d7 100644 --- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js @@ -1,5 +1,5 @@ import commitState from '~/ide/stores/modules/commit/state'; -import * as consts from '~/ide/stores/modules/commit/constants'; +import consts from '~/ide/stores/modules/commit/constants'; import * as getters from '~/ide/stores/modules/commit/getters'; describe('IDE commit module getters', () => { @@ -46,7 +46,7 @@ describe('IDE commit module getters', () => { currentBranchId: 'master', }; const localGetters = { - placeholderBranchName: 'newBranchName', + placeholderBranchName: 'placeholder-branch-name', }; beforeEach(() => { @@ -59,25 +59,28 @@ describe('IDE commit module getters', () => { expect(getters.branchName(state, null, rootState)).toBe('master'); }); - ['COMMIT_TO_NEW_BRANCH', 'COMMIT_TO_NEW_BRANCH_MR'].forEach(type => { - describe(type, () => { - beforeEach(() => { - Object.assign(state, { - commitAction: consts[type], - }); + describe('COMMIT_TO_NEW_BRANCH', () => { + beforeEach(() => { + Object.assign(state, { + commitAction: consts.COMMIT_TO_NEW_BRANCH, }); + }); - it('uses newBranchName when not empty', () => { - expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName'); + it('uses newBranchName when not empty', () => { + const newBranchName = 'nonempty-branch-name'; + Object.assign(state, { + newBranchName, }); - it('uses placeholderBranchName when state newBranchName is empty', () => { - Object.assign(state, { - newBranchName: '', - }); + expect(getters.branchName(state, localGetters, rootState)).toBe(newBranchName); + }); - expect(getters.branchName(state, localGetters, rootState)).toBe('newBranchName'); + it('uses placeholderBranchName when state newBranchName is empty', () => { + Object.assign(state, { + newBranchName: '', }); + + expect(getters.branchName(state, localGetters, rootState)).toBe('placeholder-branch-name'); }); }); }); @@ -141,4 +144,33 @@ describe('IDE commit module getters', () => { }); }); }); + + describe('shouldDisableNewMrOption', () => { + it('returns false if commitAction `COMMIT_TO_NEW_BRANCH`', () => { + state.commitAction = consts.COMMIT_TO_NEW_BRANCH; + const rootState = { + currentMergeRequest: { foo: 'bar' }, + }; + + expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy(); + }); + + it('returns false if there is no current merge request', () => { + state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + const rootState = { + currentMergeRequest: null, + }; + + expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeFalsy(); + }); + + it('returns true an MR exists and commit action is `COMMIT_TO_CURRENT_BRANCH`', () => { + state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH; + const rootState = { + currentMergeRequest: { foo: 'bar' }, + }; + + expect(getters.shouldDisableNewMrOption(state, null, null, rootState)).toBeTruthy(); + }); + }); }); diff --git a/spec/javascripts/ide/stores/mutations/tree_spec.js b/spec/javascripts/ide/stores/mutations/tree_spec.js index 67e9f7509da..7f9c978aa46 100644 --- a/spec/javascripts/ide/stores/mutations/tree_spec.js +++ b/spec/javascripts/ide/stores/mutations/tree_spec.js @@ -26,17 +26,11 @@ describe('Multi-file store tree mutations', () => { }); describe('SET_DIRECTORY_DATA', () => { - const data = [ - { - name: 'tree', - }, - { - name: 'submodule', - }, - { - name: 'blob', - }, - ]; + let data; + + beforeEach(() => { + data = [file('tree'), file('foo'), file('blob')]; + }); it('adds directory data', () => { localState.trees['project/master'] = { @@ -52,7 +46,7 @@ describe('Multi-file store tree mutations', () => { expect(tree.tree.length).toBe(3); expect(tree.tree[0].name).toBe('tree'); - expect(tree.tree[1].name).toBe('submodule'); + expect(tree.tree[1].name).toBe('foo'); expect(tree.tree[2].name).toBe('blob'); }); @@ -65,6 +59,49 @@ describe('Multi-file store tree mutations', () => { expect(localState.trees['project/master'].loading).toBe(true); }); + + it('does not override tree already in state, but merges the two with correct order', () => { + const openedFile = file('new'); + + localState.trees['project/master'] = { + loading: true, + tree: [openedFile], + }; + + mutations.SET_DIRECTORY_DATA(localState, { + data, + treePath: 'project/master', + }); + + const { tree } = localState.trees['project/master']; + + expect(tree.length).toBe(4); + expect(tree[0].name).toBe('blob'); + expect(tree[1].name).toBe('foo'); + expect(tree[2].name).toBe('new'); + expect(tree[3].name).toBe('tree'); + }); + + it('returns tree unchanged if the opened file is already in the tree', () => { + const openedFile = file('foo'); + localState.trees['project/master'] = { + loading: true, + tree: [openedFile], + }; + + mutations.SET_DIRECTORY_DATA(localState, { + data, + treePath: 'project/master', + }); + + const { tree } = localState.trees['project/master']; + + expect(tree.length).toBe(3); + + expect(tree[0].name).toBe('tree'); + expect(tree[1].name).toBe('foo'); + expect(tree[2].name).toBe('blob'); + }); }); describe('REMOVE_ALL_CHANGES_FILES', () => { diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js index 9f18034f8a3..c4f122efdda 100644 --- a/spec/javascripts/ide/stores/utils_spec.js +++ b/spec/javascripts/ide/stores/utils_spec.js @@ -235,4 +235,129 @@ describe('Multi-file store utils', () => { ]); }); }); + + describe('mergeTrees', () => { + let fromTree; + let toTree; + + beforeEach(() => { + fromTree = [file('foo')]; + toTree = [file('bar')]; + }); + + it('merges simple trees with sorting the result', () => { + toTree = [file('beta'), file('alpha'), file('gamma')]; + const res = utils.mergeTrees(fromTree, toTree); + + expect(res.length).toEqual(4); + expect(res[0].name).toEqual('alpha'); + expect(res[1].name).toEqual('beta'); + expect(res[2].name).toEqual('foo'); + expect(res[3].name).toEqual('gamma'); + expect(res[2]).toBe(fromTree[0]); + }); + + it('handles edge cases', () => { + expect(utils.mergeTrees({}, []).length).toEqual(0); + + let res = utils.mergeTrees({}, toTree); + + expect(res.length).toEqual(1); + expect(res[0].name).toEqual('bar'); + + res = utils.mergeTrees(fromTree, []); + + expect(res.length).toEqual(1); + expect(res[0].name).toEqual('foo'); + expect(res[0]).toBe(fromTree[0]); + }); + + it('merges simple trees without producing duplicates', () => { + toTree.push(file('foo')); + + const res = utils.mergeTrees(fromTree, toTree); + + expect(res.length).toEqual(2); + expect(res[0].name).toEqual('bar'); + expect(res[1].name).toEqual('foo'); + expect(res[1]).not.toBe(fromTree[0]); + }); + + it('merges nested tree into the main one without duplicates', () => { + fromTree[0].tree.push({ + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('beta.md'), + path: 'foo/alpha/beta.md', + }, + ], + }); + + toTree.push({ + ...file('foo'), + tree: [ + { + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('gamma.md'), + path: 'foo/alpha/gamma.md', + }, + ], + }, + ], + }); + + const res = utils.mergeTrees(fromTree, toTree); + + expect(res.length).toEqual(2); + expect(res[1].name).toEqual('foo'); + + const finalBranch = res[1].tree[0].tree; + + expect(finalBranch.length).toEqual(2); + expect(finalBranch[0].name).toEqual('beta.md'); + expect(finalBranch[1].name).toEqual('gamma.md'); + }); + + it('marks correct folders as opened as the parsing goes on', () => { + fromTree[0].tree.push({ + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('beta.md'), + path: 'foo/alpha/beta.md', + }, + ], + }); + + toTree.push({ + ...file('foo'), + tree: [ + { + ...file('alpha'), + path: 'foo/alpha', + tree: [ + { + ...file('gamma.md'), + path: 'foo/alpha/gamma.md', + }, + ], + }, + ], + }); + + const res = utils.mergeTrees(fromTree, toTree); + + expect(res[1].name).toEqual('foo'); + expect(res[1].opened).toEqual(true); + + expect(res[1].tree[0].name).toEqual('alpha'); + expect(res[1].tree[0].opened).toEqual(true); + }); + }); }); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index ba5d672f189..cef40117304 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -17,6 +17,7 @@ describe('Job App ', () => { const props = { endpoint: `${gl.TEST_HOST}jobs/123.json`, runnerHelpUrl: 'help/runner', + deploymentHelpUrl: 'help/deployment', runnerSettingsUrl: 'settings/ci-cd/runners', terminalPath: 'jobs/123/terminal', pagePath: `${gl.TEST_HOST}jobs/123`, @@ -253,6 +254,41 @@ describe('Job App ', () => { }); }); + describe('unmet prerequisites block', () => { + it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => { + mock.onGet(props.endpoint).replyOnce( + 200, + Object.assign({}, job, { + status: { + group: 'failed', + icon: 'status_failed', + label: 'failed', + text: 'failed', + details_path: 'path', + illustration: { + content: 'Retry this job in order to create the necessary resources.', + image: 'path', + size: 'svg-430', + title: 'Failed to create resources', + }, + }, + failure_reason: 'unmet_prerequisites', + has_trace: false, + runners: { + available: true, + }, + tags: [], + }), + ); + vm = mountComponentWithStore(Component, { props, store }); + + setTimeout(() => { + expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); + done(); + }, 0); + }); + }); + describe('environments block', () => { it('renders environment block when job has environment', done => { mock.onGet(props.endpoint).replyOnce( diff --git a/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js new file mode 100644 index 00000000000..68fcb321214 --- /dev/null +++ b/spec/javascripts/jobs/components/unmet_prerequisites_block_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import component from '~/jobs/components/unmet_prerequisites_block.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Unmet Prerequisites Block Job component', () => { + const Component = Vue.extend(component); + let vm; + const helpPath = '/user/project/clusters/index.html#troubleshooting-failed-deployment-jobs'; + + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: true, + helpPath, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders an alert with the correct message', () => { + const container = vm.$el.querySelector('.js-failed-unmet-prerequisites'); + const alertMessage = + 'This job failed because the necessary resources were not successfully created.'; + + expect(container).not.toBeNull(); + expect(container.innerHTML).toContain(alertMessage); + }); + + it('renders link to help page', () => { + const helpLink = vm.$el.querySelector('.js-help-path'); + + expect(helpLink).not.toBeNull(); + expect(helpLink.innerHTML).toContain('More information'); + expect(helpLink.getAttribute('href')).toEqual(helpPath); + }); +}); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 2084c36e484..da012e1d5f7 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -2,7 +2,7 @@ import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; import MockAdapter from 'axios-mock-adapter'; import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data'; -import BreakpointInstance from '~/breakpoints'; +import breakpointInstance from '~/breakpoints'; const PIXEL_TOLERANCE = 0.2; @@ -383,7 +383,7 @@ describe('common_utils', () => { describe('contentTop', () => { it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { - spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('sm'); + spyOn(breakpointInstance, 'isDesktop').and.returnValue(false); setFixtures(` <div class="diff-file file-title-flex-parent"> @@ -398,7 +398,7 @@ describe('common_utils', () => { }); it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { - spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('lg'); + spyOn(breakpointInstance, 'isDesktop').and.returnValue(true); setFixtures(` <div class="diff-file file-title-flex-parent"> diff --git a/spec/javascripts/lib/utils/higlight_spec.js b/spec/javascripts/lib/utils/higlight_spec.js new file mode 100644 index 00000000000..638bbf65ae9 --- /dev/null +++ b/spec/javascripts/lib/utils/higlight_spec.js @@ -0,0 +1,43 @@ +import highlight from '~/lib/utils/highlight'; + +describe('highlight', () => { + it(`should appropriately surround substring matches`, () => { + const expected = 'g<b>i</b><b>t</b>lab'; + + expect(highlight('gitlab', 'it')).toBe(expected); + }); + + it(`should return an empty string in the case of invalid inputs`, () => { + [null, undefined].forEach(input => { + expect(highlight(input, 'match')).toBe(''); + }); + }); + + it(`should return the original value if match is null, undefined, or ''`, () => { + [null, undefined].forEach(match => { + expect(highlight('gitlab', match)).toBe('gitlab'); + }); + }); + + it(`should highlight matches in non-string inputs`, () => { + const expected = '123<b>4</b><b>5</b>6'; + + expect(highlight(123456, 45)).toBe(expected); + }); + + it(`should sanitize the input string before highlighting matches`, () => { + const expected = 'hello <b>w</b>orld'; + + expect(highlight('hello <b>world</b>', 'w')).toBe(expected); + }); + + it(`should not highlight anything if no matches are found`, () => { + expect(highlight('gitlab', 'hello')).toBe('gitlab'); + }); + + it(`should allow wrapping elements to be customized`, () => { + const expected = '1<hello>2</hello>3'; + + expect(highlight('123', '2', '<hello>', '</hello>')).toBe(expected); + }); +}); diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index 549a7935c0f..41a6c04efb9 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlAreaChart } from '@gitlab/ui/dist/charts'; +import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; import Area from '~/monitoring/components/charts/area.vue'; import MonitoringStore from '~/monitoring/stores/monitoring_store'; @@ -65,7 +65,7 @@ describe('Area component', () => { expect(props.data).toBe(areaChart.vm.chartData); expect(props.option).toBe(areaChart.vm.chartOptions); expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText); - expect(props.thresholds).toBe(areaChart.props('alertData')); + expect(props.thresholds).toBe(areaChart.vm.thresholds); }); it('recieves a tooltip title', () => { @@ -105,12 +105,13 @@ describe('Area component', () => { seriesName: areaChart.vm.chartData[0].name, componentSubType: type, value: [mockDate, 5.55555], + seriesIndex: 0, }, ], value: mockDate, }); - describe('series is of line type', () => { + describe('when series is of line type', () => { beforeEach(() => { areaChart.vm.formatTooltipText(generateSeriesData('line')); }); @@ -120,18 +121,20 @@ describe('Area component', () => { }); it('formats tooltip content', () => { - expect(areaChart.vm.tooltip.content).toEqual([{ name: 'Core Usage', value: '5.556' }]); + const name = 'Core Usage'; + const value = '5.556'; + const seriesLabel = areaChart.find(GlChartSeriesLabel); + + expect(seriesLabel.vm.color).toBe(''); + expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); + expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]); expect( - shallowWrapperContainsSlotText( - areaChart.find(GlAreaChart), - 'tooltipContent', - 'Core Usage 5.556', - ), + shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value), ).toBe(true); }); }); - describe('series is of scatter type', () => { + describe('when series is of scatter type', () => { beforeEach(() => { areaChart.vm.formatTooltipText(generateSeriesData('scatter')); }); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 454777fa912..ce2c6c43c0f 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; +import { timeWindows } from '~/monitoring/constants'; import axios from '~/lib/utils/axios_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; @@ -50,7 +51,7 @@ describe('Dashboard', () => { it('shows a getting started empty state when no metrics are present', () => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData, + propsData: { ...propsData, showTimeWindowDropdown: false }, }); expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); @@ -66,7 +67,7 @@ describe('Dashboard', () => { it('shows up a loading state', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true }, + propsData: { ...propsData, hasMetrics: true, showTimeWindowDropdown: false }, }); Vue.nextTick(() => { @@ -78,7 +79,12 @@ describe('Dashboard', () => { it('hides the legend when showLegend is false', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showLegend: false }, + propsData: { + ...propsData, + hasMetrics: true, + showLegend: false, + showTimeWindowDropdown: false, + }, }); setTimeout(() => { @@ -92,7 +98,12 @@ describe('Dashboard', () => { it('hides the group panels when showPanels is false', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); setTimeout(() => { @@ -106,7 +117,12 @@ describe('Dashboard', () => { it('renders the environments dropdown with a number of environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); component.store.storeEnvironmentsData(environmentData); @@ -124,7 +140,12 @@ describe('Dashboard', () => { it('hides the environments dropdown list when there is no environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); component.store.storeEnvironmentsData([]); @@ -142,7 +163,12 @@ describe('Dashboard', () => { it('renders the environments dropdown with a single is-active element', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); component.store.storeEnvironmentsData(environmentData); @@ -166,6 +192,7 @@ describe('Dashboard', () => { hasMetrics: true, showPanels: false, environmentsEndpoint: '', + showTimeWindowDropdown: false, }, }); @@ -176,6 +203,51 @@ describe('Dashboard', () => { done(); }); }); + + it('does not show the time window dropdown when the feature flag is not set', done => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, + }); + + setTimeout(() => { + const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); + + expect(timeWindowDropdown).toBeNull(); + + done(); + }); + }); + + it('renders the time window dropdown with a set of options', done => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: true, + }, + }); + const numberOfTimeWindows = Object.keys(timeWindows).length; + + setTimeout(() => { + const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown'); + const timeWindowDropdownEls = component.$el.querySelectorAll( + '.js-time-window-dropdown .dropdown-item', + ); + + expect(timeWindowDropdown).not.toBeNull(); + expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows); + + done(); + }); + }); }); describe('when the window resizes', () => { @@ -191,7 +263,12 @@ describe('Dashboard', () => { it('sets elWidth to page width when the sidebar is resized', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), - propsData: { ...propsData, hasMetrics: true, showPanels: false }, + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + }, }); expect(component.elWidth).toEqual(0); diff --git a/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js new file mode 100644 index 00000000000..29760f79c3c --- /dev/null +++ b/spec/javascripts/related_merge_requests/components/related_merge_requests_spec.js @@ -0,0 +1,89 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; +import RelatedMergeRequests from '~/related_merge_requests/components/related_merge_requests.vue'; +import createStore from '~/related_merge_requests/store/index'; + +const FIXTURE_PATH = 'issues/related_merge_requests.json'; +const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests'; +const localVue = createLocalVue(); + +describe('RelatedMergeRequests', () => { + let wrapper; + let mock; + let mockData; + + beforeEach(done => { + loadFixtures(FIXTURE_PATH); + mockData = getJSONFixture(FIXTURE_PATH); + mock = new MockAdapter(axios); + mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 }); + + wrapper = mount(RelatedMergeRequests, { + localVue, + sync: false, + store: createStore(), + propsData: { + endpoint: API_ENDPOINT, + projectNamespace: 'gitlab-org', + projectPath: 'gitlab-ce', + }, + }); + + setTimeout(done); + }); + + afterEach(() => { + wrapper.destroy(); + mock.restore(); + }); + + describe('methods', () => { + describe('getAssignees', () => { + const assignees = [{ name: 'foo' }, { name: 'bar' }]; + + describe('when there is assignees array', () => { + it('should return assignees array', () => { + const mr = { assignees }; + + expect(wrapper.vm.getAssignees(mr)).toEqual(assignees); + }); + }); + + it('should return an array with single assingee', () => { + const mr = { assignee: assignees[0] }; + + expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]); + }); + + it('should return empty array when assignee is not set', () => { + expect(wrapper.vm.getAssignees({})).toEqual([]); + expect(wrapper.vm.getAssignees({ assignee: null })).toEqual([]); + }); + }); + }); + + describe('template', () => { + it('should render related merge request items', () => { + expect(wrapper.find('.js-items-count').text()).toEqual('2'); + expect(wrapper.findAll(RelatedIssuableItem).length).toEqual(2); + + const props = wrapper + .findAll(RelatedIssuableItem) + .at(1) + .props(); + const data = mockData[1]; + + expect(props.idKey).toEqual(data.id); + expect(props.pathIdSeparator).toEqual('!'); + expect(props.pipelineStatus).toBe(data.head_pipeline.detailed_status); + expect(props.assignees).toEqual([data.assignee]); + expect(props.isMergeRequest).toBe(true); + expect(props.confidential).toEqual(false); + expect(props.title).toEqual(data.title); + expect(props.state).toEqual(data.state); + expect(props.createdAt).toEqual(data.created_at); + }); + }); +}); diff --git a/spec/javascripts/related_merge_requests/store/actions_spec.js b/spec/javascripts/related_merge_requests/store/actions_spec.js new file mode 100644 index 00000000000..65e436fbb17 --- /dev/null +++ b/spec/javascripts/related_merge_requests/store/actions_spec.js @@ -0,0 +1,110 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import * as types from '~/related_merge_requests/store/mutation_types'; +import actionsModule, * as actions from '~/related_merge_requests/store/actions'; +import testAction from 'spec/helpers/vuex_action_helper'; + +describe('RelatedMergeRequest store actions', () => { + let state; + let flashSpy; + let mock; + + beforeEach(() => { + state = { + apiEndpoint: '/api/related_merge_requests', + }; + flashSpy = spyOnDependency(actionsModule, 'createFlash'); + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('setInitialState', () => { + it('commits types.SET_INITIAL_STATE with given props', done => { + const props = { a: 1, b: 2 }; + + testAction( + actions.setInitialState, + props, + {}, + [{ type: types.SET_INITIAL_STATE, payload: props }], + [], + done, + ); + }); + }); + + describe('requestData', () => { + it('commits types.REQUEST_DATA', done => { + testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], [], done); + }); + }); + + describe('receiveDataSuccess', () => { + it('commits types.RECEIVE_DATA_SUCCESS with data', done => { + const data = { a: 1, b: 2 }; + + testAction( + actions.receiveDataSuccess, + data, + {}, + [{ type: types.RECEIVE_DATA_SUCCESS, payload: data }], + [], + done, + ); + }); + }); + + describe('receiveDataError', () => { + it('commits types.RECEIVE_DATA_ERROR', done => { + testAction( + actions.receiveDataError, + null, + {}, + [{ type: types.RECEIVE_DATA_ERROR }], + [], + done, + ); + }); + }); + + describe('fetchMergeRequests', () => { + describe('for a successful request', () => { + it('should dispatch success action', done => { + const data = { a: 1 }; + mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(200, data, { 'x-total': 2 }); + + testAction( + actions.fetchMergeRequests, + null, + state, + [], + [{ type: 'requestData' }, { type: 'receiveDataSuccess', payload: { data, total: 2 } }], + done, + ); + }); + }); + + describe('for a failing request', () => { + it('should dispatch error action', done => { + mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(400); + + testAction( + actions.fetchMergeRequests, + null, + state, + [], + [{ type: 'requestData' }, { type: 'receiveDataError' }], + () => { + expect(flashSpy).toHaveBeenCalledTimes(1); + expect(flashSpy).toHaveBeenCalledWith(jasmine.stringMatching('Something went wrong')); + + done(); + }, + ); + }); + }); + }); +}); diff --git a/spec/javascripts/related_merge_requests/store/mutations_spec.js b/spec/javascripts/related_merge_requests/store/mutations_spec.js new file mode 100644 index 00000000000..21b6e26376b --- /dev/null +++ b/spec/javascripts/related_merge_requests/store/mutations_spec.js @@ -0,0 +1,49 @@ +import mutations from '~/related_merge_requests/store/mutations'; +import * as types from '~/related_merge_requests/store/mutation_types'; + +describe('RelatedMergeRequests Store Mutations', () => { + describe('SET_INITIAL_STATE', () => { + it('should set initial state according to given data', () => { + const apiEndpoint = '/api'; + const state = {}; + + mutations[types.SET_INITIAL_STATE](state, { apiEndpoint }); + + expect(state.apiEndpoint).toEqual(apiEndpoint); + }); + }); + + describe('REQUEST_DATA', () => { + it('should set loading flag', () => { + const state = {}; + + mutations[types.REQUEST_DATA](state); + + expect(state.isFetchingMergeRequests).toEqual(true); + }); + }); + + describe('RECEIVE_DATA_SUCCESS', () => { + it('should set loading flag and data', () => { + const state = {}; + const mrs = [1, 2, 3]; + + mutations[types.RECEIVE_DATA_SUCCESS](state, { data: mrs, total: mrs.length }); + + expect(state.isFetchingMergeRequests).toEqual(false); + expect(state.mergeRequests).toEqual(mrs); + expect(state.totalCount).toEqual(mrs.length); + }); + }); + + describe('RECEIVE_DATA_ERROR', () => { + it('should set loading and error flags', () => { + const state = {}; + + mutations[types.RECEIVE_DATA_ERROR](state); + + expect(state.isFetchingMergeRequests).toEqual(false); + expect(state.hasErrorFetchingMergeRequests).toEqual(true); + }); + }); +}); diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js deleted file mode 100644 index 85cfe71281f..00000000000 --- a/spec/javascripts/serverless/components/functions_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import Vue from 'vue'; - -import functionsComponent from '~/serverless/components/functions.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import ServerlessStore from '~/serverless/stores/serverless_store'; - -import { mockServerlessFunctions } from '../mock_data'; - -const createComponent = ( - functions, - installed = true, - loadingData = true, - hasFunctionData = true, -) => { - const component = Vue.extend(functionsComponent); - - return mountComponent(component, { - functions, - installed, - clustersPath: '/testClusterPath', - helpPath: '/helpPath', - loadingData, - hasFunctionData, - }); -}; - -describe('functionsComponent', () => { - it('should render empty state when Knative is not installed', () => { - const vm = createComponent({}, false); - - expect(vm.$el.querySelector('div.row').classList.contains('js-empty-state')).toBe(true); - expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual( - 'Getting started with serverless', - ); - - vm.$destroy(); - }); - - it('should render a loading component', () => { - const vm = createComponent({}); - - expect(vm.$el.querySelector('.gl-responsive-table-row')).not.toBe(null); - expect(vm.$el.querySelector('div.animation-container')).not.toBe(null); - }); - - it('should render empty state when there is no function data', () => { - const vm = createComponent({}, true, false, false); - - expect( - vm.$el.querySelector('.empty-state, .js-empty-state').classList.contains('js-empty-state'), - ).toBe(true); - - expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual( - 'No functions available', - ); - - vm.$destroy(); - }); - - it('should render the functions list', () => { - const store = new ServerlessStore(false, '/cluster_path', 'help_path'); - store.updateFunctionsFromServer(mockServerlessFunctions); - const vm = createComponent(store.state.functions, true, false); - - expect(vm.$el.querySelector('div.groups-list-tree-container')).not.toBe(null); - expect(vm.$el.querySelector('#env-global').classList.contains('has-children')).toBe(true); - }); -}); diff --git a/spec/javascripts/serverless/stores/serverless_store_spec.js b/spec/javascripts/serverless/stores/serverless_store_spec.js deleted file mode 100644 index 72fd903d7d1..00000000000 --- a/spec/javascripts/serverless/stores/serverless_store_spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import ServerlessStore from '~/serverless/stores/serverless_store'; -import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; - -describe('Serverless Functions Store', () => { - let store; - - beforeEach(() => { - store = new ServerlessStore(false, '/cluster_path', 'help_path'); - }); - - describe('#updateFunctionsFromServer', () => { - it('should pass an empty hash object', () => { - store.updateFunctionsFromServer(); - - expect(store.state.functions).toEqual({}); - }); - - it('should group functions to one global environment', () => { - const mockServerlessData = mockServerlessFunctions; - store.updateFunctionsFromServer(mockServerlessData); - - expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*'])); - expect(store.state.functions['*'].length).toEqual(2); - }); - - it('should group functions to multiple environments', () => { - const mockServerlessData = mockServerlessFunctionsDiffEnv; - store.updateFunctionsFromServer(mockServerlessData); - - expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*'])); - expect(store.state.functions['*'].length).toEqual(1); - expect(store.state.functions.test.length).toEqual(1); - expect(store.state.functions.test[0].name).toEqual('testfunc2'); - }); - }); -}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 235a17d13b0..87ef0885d8c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -69,7 +69,7 @@ window.gl = window.gl || {}; window.gl.TEST_HOST = TEST_HOST; window.gon = window.gon || {}; window.gon.test_env = true; -window.gon.ee = process.env.EE; +window.gon.ee = process.env.IS_GITLAB_EE; gon.relative_url_root = ''; let hasUnhandledPromiseRejections = false; @@ -124,7 +124,7 @@ const axiosDefaultAdapter = getDefaultAdapter(); // render all of our tests const testContexts = [require.context('spec', true, /_spec$/)]; -if (process.env.EE) { +if (process.env.IS_GITLAB_EE) { testContexts.push(require.context('ee_spec', true, /_spec$/)); } @@ -213,7 +213,7 @@ if (process.env.BABEL_ENV === 'coverage') { describe('Uncovered files', function() { const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)]; - if (process.env.EE) { + if (process.env.IS_GITLAB_EE) { sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/)); } diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js index d1fd899c1a8..7da69e3fa84 100644 --- a/spec/javascripts/vue_shared/components/file_row_spec.js +++ b/spec/javascripts/vue_shared/components/file_row_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import FileRow from '~/vue_shared/components/file_row.vue'; +import FileRowExtra from '~/ide/components/file_row_extra.vue'; import { file } from 'spec/ide/helpers'; import mountComponent from '../../helpers/vue_mount_component_helper'; @@ -16,6 +17,10 @@ describe('File row component', () => { vm.$destroy(); }); + const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown'); + const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button'); + const findFileRow = () => vm.$el.querySelector('.file-row'); + it('renders name', () => { createComponent({ file: file('t4'), @@ -84,4 +89,59 @@ describe('File row component', () => { expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null); }); + + describe('new dropdown', () => { + beforeEach(() => { + createComponent({ + file: file('t5'), + level: 1, + extraComponent: FileRowExtra, + }); + }); + + it('renders in extra component', () => { + expect(findNewDropdown()).not.toBe(null); + }); + + it('is hidden at start', () => { + expect(findNewDropdown()).not.toHaveClass('show'); + }); + + it('is opened when button is clicked', done => { + expect(vm.dropdownOpen).toBe(false); + findNewDropdownButton().dispatchEvent(new Event('click')); + + vm.$nextTick() + .then(() => { + expect(vm.dropdownOpen).toBe(true); + expect(findNewDropdown()).toHaveClass('show'); + }) + .then(done) + .catch(done.fail); + }); + + describe('when opened', () => { + beforeEach(() => { + vm.dropdownOpen = true; + }); + + it('stays open when button triggers mouseout', () => { + findNewDropdownButton().dispatchEvent(new Event('mouseout')); + + expect(vm.dropdownOpen).toBe(true); + }); + + it('stays open when button triggers mouseleave', () => { + findNewDropdownButton().dispatchEvent(new Event('mouseleave')); + + expect(vm.dropdownOpen).toBe(true); + }); + + it('closes when row triggers mouseleave', () => { + findFileRow().dispatchEvent(new Event('mouseleave')); + + expect(vm.dropdownOpen).toBe(false); + }); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js new file mode 100644 index 00000000000..b95183747bb --- /dev/null +++ b/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js @@ -0,0 +1,110 @@ +import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { trimText } from 'spec/helpers/vue_component_helper'; + +const localVue = createLocalVue(); + +describe('ProjectListItem component', () => { + const Component = localVue.extend(ProjectListItem); + let wrapper; + let vm; + let options; + loadJSONFixtures('projects.json'); + const project = getJSONFixture('projects.json')[0]; + + beforeEach(() => { + options = { + propsData: { + project, + selected: false, + }, + sync: false, + localVue, + }; + }); + + afterEach(() => { + wrapper.vm.$destroy(); + }); + + it('does not render a check mark icon if selected === false', () => { + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-unselected')).toBe(true); + }); + + it('renders a check mark icon if selected === true', () => { + options.propsData.selected = true; + + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-selected-icon.js-selected')).toBe(true); + }); + + it(`emits a "clicked" event when clicked`, () => { + wrapper = shallowMount(Component, options); + ({ vm } = wrapper); + + spyOn(vm, '$emit'); + wrapper.vm.onClick(); + + expect(wrapper.vm.$emit).toHaveBeenCalledWith('click'); + }); + + it(`renders the project avatar`, () => { + wrapper = shallowMount(Component, options); + + expect(wrapper.contains('.js-project-avatar')).toBe(true); + }); + + it(`renders a simple namespace name with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b'; + + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); + + expect(renderedNamespace).toBe('a /'); + }); + + it(`renders a properly truncated namespace with a trailing slash`, () => { + options.propsData.project.name_with_namespace = 'a / b / c / d / e / f'; + + wrapper = shallowMount(Component, options); + const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text()); + + expect(renderedNamespace).toBe('a / ... / e /'); + }); + + it(`renders the project name`, () => { + options.propsData.project.name = 'my-test-project'; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').text()); + + expect(renderedName).toBe('my-test-project'); + }); + + it(`renders the project name with highlighting in the case of a search query match`, () => { + options.propsData.project.name = 'my-test-project'; + options.propsData.matcher = 'pro'; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-test-<b>p</b><b>r</b><b>o</b>ject'; + + expect(renderedName).toContain(expected); + }); + + it('prevents search query and project name XSS', () => { + const alertSpy = spyOn(window, 'alert'); + options.propsData.project.name = "my-xss-pro<script>alert('XSS');</script>ject"; + options.propsData.matcher = "pro<script>alert('XSS');</script>"; + + wrapper = shallowMount(Component, options); + const renderedName = trimText(wrapper.find('.js-project-name').html()); + const expected = 'my-xss-project'; + + expect(renderedName).toContain(expected); + expect(alertSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js new file mode 100644 index 00000000000..ba9ec8f2f19 --- /dev/null +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -0,0 +1,132 @@ +import Vue from 'vue'; +import _ from 'underscore'; +import ProjectSelector from '~/vue_shared/components/project_selector/project_selector.vue'; +import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; +import { shallowMount } from '@vue/test-utils'; +import { trimText } from 'spec/helpers/vue_component_helper'; + +describe('ProjectSelector component', () => { + let wrapper; + let vm; + loadJSONFixtures('projects.json'); + const allProjects = getJSONFixture('projects.json'); + const searchResults = allProjects.slice(0, 5); + let selected = []; + selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8)); + + beforeEach(() => { + jasmine.clock().install(); + + wrapper = shallowMount(Vue.extend(ProjectSelector), { + propsData: { + projectSearchResults: searchResults, + selectedProjects: selected, + showNoResultsMessage: false, + showMinimumSearchQueryMessage: false, + showLoadingIndicator: false, + showSearchErrorMessage: false, + }, + attachToDocument: true, + }); + + ({ vm } = wrapper); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + vm.$destroy(); + }); + + it('renders the search results', () => { + expect(wrapper.findAll('.js-project-list-item').length).toBe(5); + }); + + it(`triggers a (debounced) search when the search input value changes`, () => { + spyOn(vm, '$emit'); + const query = 'my test query!'; + const searchInput = wrapper.find('.js-project-selector-input'); + searchInput.setValue(query); + searchInput.trigger('input'); + + expect(vm.$emit).not.toHaveBeenCalledWith(); + jasmine.clock().tick(501); + + expect(vm.$emit).toHaveBeenCalledWith('searched', query); + }); + + it(`debounces the search input`, () => { + spyOn(vm, '$emit'); + const searchInput = wrapper.find('.js-project-selector-input'); + + const updateSearchQuery = (count = 0) => { + if (count === 10) { + jasmine.clock().tick(101); + + expect(vm.$emit).toHaveBeenCalledTimes(1); + expect(vm.$emit).toHaveBeenCalledWith('searched', `search query #9`); + } else { + searchInput.setValue(`search query #${count}`); + searchInput.trigger('input'); + + jasmine.clock().tick(400); + updateSearchQuery(count + 1); + } + }; + + updateSearchQuery(); + }); + + it(`includes a placeholder in the search box`, () => { + expect(wrapper.find('.js-project-selector-input').attributes('placeholder')).toBe( + 'Search your projects', + ); + }); + + it(`triggers a "projectClicked" event when a project is clicked`, () => { + spyOn(vm, '$emit'); + wrapper.find(ProjectListItem).vm.$emit('click', _.first(searchResults)); + + expect(vm.$emit).toHaveBeenCalledWith('projectClicked', _.first(searchResults)); + }); + + it(`shows a "no results" message if showNoResultsMessage === true`, () => { + wrapper.setProps({ showNoResultsMessage: true }); + + expect(wrapper.contains('.js-no-results-message')).toBe(true); + + const noResultsEl = wrapper.find('.js-no-results-message'); + + expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search'); + }); + + it(`shows a "minimum seach query" message if showMinimumSearchQueryMessage === true`, () => { + wrapper.setProps({ showMinimumSearchQueryMessage: true }); + + expect(wrapper.contains('.js-minimum-search-query-message')).toBe(true); + + const minimumSearchEl = wrapper.find('.js-minimum-search-query-message'); + + expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search'); + }); + + it(`shows a error message if showSearchErrorMessage === true`, () => { + wrapper.setProps({ showSearchErrorMessage: true }); + + expect(wrapper.contains('.js-search-error-message')).toBe(true); + + const errorMessageEl = wrapper.find('.js-search-error-message'); + + expect(trimText(errorMessageEl.text())).toEqual( + 'Something went wrong, unable to search projects', + ); + }); + + it(`focuses the input element when the focusSearchInput() method is called`, () => { + const input = wrapper.find('.js-project-selector-input'); + + expect(document.activeElement).not.toBe(input.element); + vm.focusSearchInput(); + + expect(document.activeElement).toBe(input.element); + }); +}); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js index 5cf6afebd7e..0689fc1cf1f 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js @@ -45,12 +45,21 @@ describe('DropdownButtonComponent', () => { }); const vmMoreLabels = createComponent(mockMoreLabels); - expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more'); + expect(vmMoreLabels.dropdownToggleText).toBe( + `Foo Label +${mockMoreLabels.labels.length - 1} more`, + ); vmMoreLabels.$destroy(); }); it('returns first label name when `labels` prop has only one item present', () => { - expect(vm.dropdownToggleText).toBe('Foo Label'); + const singleLabel = Object.assign({}, componentConfig, { + labels: [mockLabels[0]], + }); + const vmSingleLabel = createComponent(singleLabel); + + expect(vmSingleLabel.dropdownToggleText).toBe(mockLabels[0].title); + + vmSingleLabel.$destroy(); }); }); }); @@ -73,7 +82,7 @@ describe('DropdownButtonComponent', () => { const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text'); expect(dropdownToggleTextEl).not.toBeNull(); - expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label'); + expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label +1 more'); }); it('renders dropdown button icon', () => { diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js index 804b33422bd..cb49fa31d20 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -35,9 +35,12 @@ describe('DropdownValueCollapsedComponent', () => { }); it('returns labels names separated by coma when `labels` prop has more than one item', () => { - const vmMoreLabels = createComponent(mockLabels.concat(mockLabels)); + const labels = mockLabels.concat(mockLabels); + const vmMoreLabels = createComponent(labels); - expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label'); + const expectedText = labels.map(label => label.title).join(', '); + + expect(vmMoreLabels.labelsList).toBe(expectedText); vmMoreLabels.$destroy(); }); @@ -49,14 +52,19 @@ describe('DropdownValueCollapsedComponent', () => { const vmMoreLabels = createComponent(mockMoreLabels); - expect(vmMoreLabels.labelsList).toBe( - 'Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more', - ); + const expectedText = `${mockMoreLabels + .slice(0, 5) + .map(label => label.title) + .join(', ')}, and ${mockMoreLabels.length - 5} more`; + + expect(vmMoreLabels.labelsList).toBe(expectedText); vmMoreLabels.$destroy(); }); it('returns first label name when `labels` prop has only one item present', () => { - expect(vm.labelsList).toBe('Foo Label'); + const text = mockLabels.map(label => label.title).join(', '); + + expect(vm.labelsList).toBe(text); }); }); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js index 3fff781594f..35a9c300953 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import $ from 'jquery'; import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; @@ -15,6 +16,7 @@ const createComponent = ( return mountComponent(Component, { labels, labelFilterBasePath, + enableScopedLabels: true, }); }; @@ -67,6 +69,26 @@ describe('DropdownValueComponent', () => { expect(styleObj.backgroundColor).toBe(label.color); }); }); + + describe('scopedLabelsDescription', () => { + it('returns html for tooltip', () => { + const html = vm.scopedLabelsDescription(mockLabels[1]); + const $el = $.parseHTML(html); + + expect($el[0]).toHaveClass('scoped-label-tooltip-title'); + expect($el[2].textContent).toEqual(mockLabels[1].description); + }); + }); + + describe('showScopedLabels', () => { + it('returns true if the label is scoped label', () => { + expect(vm.showScopedLabels(mockLabels[1])).toBe(true); + }); + + it('returns false when label is a regular label', () => { + expect(vm.showScopedLabels(mockLabels[0])).toBe(false); + }); + }); }); describe('template', () => { @@ -91,15 +113,25 @@ describe('DropdownValueComponent', () => { ); }); - it('renders label element with tooltip and styles based on label details', () => { + it('renders label element and styles based on label details', () => { const labelEl = vm.$el.querySelector('a span.badge.color-label'); expect(labelEl).not.toBeNull(); - expect(labelEl.dataset.placement).toBe('bottom'); - expect(labelEl.dataset.container).toBe('body'); - expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description); expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);'); expect(labelEl.innerText.trim()).toBe(mockLabels[0].title); }); + + describe('label is of scoped-label type', () => { + it('renders a scoped-label-wrapper span to incorporate 2 anchors', () => { + expect(vm.$el.querySelector('span.scoped-label-wrapper')).not.toBeNull(); + }); + + it('renders anchor tag containing question icon', () => { + const anchor = vm.$el.querySelector('.scoped-label-wrapper a.scoped-label'); + + expect(anchor).not.toBeNull(); + expect(anchor.querySelector('i.fa-question-circle')).not.toBeNull(); + }); + }); }); }); diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js index 3fcb91b6f5e..70025f041a7 100644 --- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js @@ -6,6 +6,13 @@ export const mockLabels = [ color: '#BADA55', text_color: '#FFFFFF', }, + { + id: 27, + title: 'Foo::Bar', + description: 'Foobar', + color: '#0033CC', + text_color: '#FFFFFF', + }, ]; export const mockSuggestedColors = [ diff --git a/spec/lib/api/helpers/custom_validators_spec.rb b/spec/lib/api/helpers/custom_validators_spec.rb index 9945d598a14..aed86b21cb7 100644 --- a/spec/lib/api/helpers/custom_validators_spec.rb +++ b/spec/lib/api/helpers/custom_validators_spec.rb @@ -21,7 +21,7 @@ describe API::Helpers::CustomValidators do end context 'invalid parameters' do - it 'should raise a validation error' do + it 'raises a validation error' do expect_validation_error({ 'test' => 'some_value' }) end end @@ -44,7 +44,7 @@ describe API::Helpers::CustomValidators do end context 'invalid parameters' do - it 'should raise a validation error' do + it 'raises a validation error' do expect_validation_error({ 'test' => 'some_other_string' }) end end @@ -67,7 +67,7 @@ describe API::Helpers::CustomValidators do end context 'invalid parameters' do - it 'should raise a validation error' do + it 'raises a validation error' do expect_validation_error({ 'test' => 'some_other_string' }) end end diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb index b645e49bd43..5b3f679084e 100644 --- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -13,6 +13,6 @@ describe Banzai::Filter::BlockquoteFenceFilter do end it 'allows trailing whitespace on blockquote fence lines' do - expect(filter(">>> \ntest\n>>> ")).to eq("> test") + expect(filter(">>> \ntest\n>>> ")).to eq("\n> test\n") end end diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb index 8235c411eb7..6f7acfe7072 100644 --- a/spec/lib/banzai/filter/plantuml_filter_spec.rb +++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Banzai::Filter::PlantumlFilter do include FilterSpecHelper - it 'should replace plantuml pre tag with img tag' do + it 'replaces plantuml pre tag with img tag' do stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>' @@ -12,7 +12,7 @@ describe Banzai::Filter::PlantumlFilter do expect(doc.to_s).to eq output end - it 'should not replace plantuml pre tag with img tag if disabled' do + it 'does not replace plantuml pre tag with img tag if disabled' do stub_application_setting(plantuml_enabled: false) input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' output = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' @@ -21,7 +21,7 @@ describe Banzai::Filter::PlantumlFilter do expect(doc.to_s).to eq output end - it 'should not replace plantuml pre tag with img tag if url is invalid' do + it 'does not replace plantuml pre tag with img tag if url is invalid' do stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> PlantUML Error: cannot connect to PlantUML server at "invalid"</pre></div></div>' diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb index 494c0561975..b9ffe895bf0 100644 --- a/spec/lib/forever_spec.rb +++ b/spec/lib/forever_spec.rb @@ -5,7 +5,7 @@ describe Forever do subject { described_class.date } context 'when using PostgreSQL' do - it 'should return Postgresql future date' do + it 'returns Postgresql future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) expect(subject).to eq(described_class::POSTGRESQL_DATE) @@ -13,7 +13,7 @@ describe Forever do end context 'when using MySQL' do - it 'should return MySQL future date' do + it 'returns MySQL future date' do allow(Gitlab::Database).to receive(:postgresql?).and_return(false) expect(subject).to eq(described_class::MYSQL_DATE) diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb index 812e0cc6947..128e118ac17 100644 --- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : end shared_examples 'consistent kubernetes namespace attributes' do - it 'should populate namespace and service account information' do + it 'populates namespace and service account information' do migration.perform clusters_with_namespace.each do |cluster| @@ -41,7 +41,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : context 'when no Clusters::Project has a Clusters::KubernetesNamespace' do let(:cluster_projects) { cluster_projects_table.all } - it 'should create a Clusters::KubernetesNamespace per Clusters::Project' do + it 'creates a Clusters::KubernetesNamespace per Clusters::Project' do expect do migration.perform end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects_table.count) @@ -57,7 +57,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : create_kubernetes_namespace(clusters_table.all) end - it 'should not create any Clusters::KubernetesNamespace' do + it 'does not create any Clusters::KubernetesNamespace' do expect do migration.perform end.not_to change(Clusters::KubernetesNamespace, :count) @@ -78,7 +78,7 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, : end.to change(Clusters::KubernetesNamespace, :count).by(with_no_kubernetes_namespace.count) end - it 'should not modify clusters with Clusters::KubernetesNamespace' do + it 'does not modify clusters with Clusters::KubernetesNamespace' do migration.perform with_kubernetes_namespace.each do |cluster| diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb index 8582af96199..0e73c8c59c9 100644 --- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb @@ -41,7 +41,7 @@ describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, sch migration.perform(1, 3) end - it 'it creates the fork network' do + it 'creates the fork network' do expect(fork_network1).not_to be_nil expect(fork_network2).not_to be_nil end diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index fa39b32d7ab..dd536a241bd 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Ci::Config::External::File::Base do context 'when a location is present' do let(:location) { 'some-location' } - it 'should return true' do + it 'returns true' do expect(subject).to be_matching end end @@ -34,7 +34,7 @@ describe Gitlab::Ci::Config::External::File::Base do context 'with a location is missing' do let(:location) { nil } - it 'should return false' do + it 'returns false' do expect(subject).not_to be_matching end end diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index dc14b07287e..9451db9522a 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'when a local is specified' do let(:params) { { local: 'file' } } - it 'should return true' do + it 'returns true' do expect(local_file).to be_matching end end @@ -23,7 +23,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'with a missing local' do let(:params) { { local: nil } } - it 'should return false' do + it 'returns false' do expect(local_file).not_to be_matching end end @@ -31,7 +31,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'with a missing local key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(local_file).not_to be_matching end end @@ -45,7 +45,7 @@ describe Gitlab::Ci::Config::External::File::Local do allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return("image: 'ruby2:2'") end - it 'should return true' do + it 'returns true' do expect(local_file.valid?).to be_truthy end end @@ -53,7 +53,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'when is not a valid local path' do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } - it 'should return false' do + it 'returns false' do expect(local_file.valid?).to be_falsy end end @@ -61,7 +61,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'when is not a yaml file' do let(:location) { '/config/application.rb' } - it 'should return false' do + it 'returns false' do expect(local_file.valid?).to be_falsy end end @@ -84,7 +84,7 @@ describe Gitlab::Ci::Config::External::File::Local do allow_any_instance_of(described_class).to receive(:fetch_local_content).and_return(local_file_content) end - it 'should return the content of the file' do + it 'returns the content of the file' do expect(local_file.content).to eq(local_file_content) end end @@ -92,7 +92,7 @@ describe Gitlab::Ci::Config::External::File::Local do context 'with an invalid file' do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } - it 'should be nil' do + it 'is nil' do expect(local_file.content).to be_nil end end @@ -101,7 +101,7 @@ describe Gitlab::Ci::Config::External::File::Local do describe '#error_message' do let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' } - it 'should return an error message' do + it 'returns an error message' do expect(local_file.error_message).to eq("Local file `#{location}` does not exist!") end end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 6e89bb1b30f..4acb4f324d3 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'when a file and project is specified' do let(:params) { { file: 'file.yml', project: 'project' } } - it 'should return true' do + it 'returns true' do expect(project_file).to be_matching end end @@ -27,7 +27,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'with only file is specified' do let(:params) { { file: 'file.yml' } } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_matching end end @@ -35,7 +35,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'with only project is specified' do let(:params) { { project: 'project' } } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_matching end end @@ -43,7 +43,7 @@ describe Gitlab::Ci::Config::External::File::Project do context 'with a missing local key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_matching end end @@ -61,14 +61,14 @@ describe Gitlab::Ci::Config::External::File::Project do stub_project_blob(root_ref_sha, '/file.yml') { 'image: ruby:2.1' } end - it 'should return true' do + it 'returns true' do expect(project_file).to be_valid end context 'when user does not have permission to access file' do let(:context_user) { create(:user) } - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` not found or access denied!") end @@ -86,7 +86,7 @@ describe Gitlab::Ci::Config::External::File::Project do stub_project_blob(ref_sha, '/file.yml') { 'image: ruby:2.1' } end - it 'should return true' do + it 'returns true' do expect(project_file).to be_valid end end @@ -102,7 +102,7 @@ describe Gitlab::Ci::Config::External::File::Project do stub_project_blob(root_ref_sha, '/file.yml') { '' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` file `/file.yml` is empty!") end @@ -113,7 +113,7 @@ describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, ref: 'I-Do-Not-Exist', file: '/file.yml' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` reference `I-Do-Not-Exist` does not exist!") end @@ -124,7 +124,7 @@ describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, file: '/invalid-file.yml' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include("Project `#{project.full_path}` file `/invalid-file.yml` does not exist!") end @@ -135,7 +135,7 @@ describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, file: '/invalid-file' } end - it 'should return false' do + it 'returns false' do expect(project_file).not_to be_valid expect(project_file.error_message).to include('Included file `/invalid-file` does not have YAML extension!') end diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb index c5b32c29759..d8a61618e77 100644 --- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when a remote is specified' do let(:params) { { remote: 'http://remote' } } - it 'should return true' do + it 'returns true' do expect(remote_file).to be_matching end end @@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with a missing remote' do let(:params) { { remote: nil } } - it 'should return false' do + it 'returns false' do expect(remote_file).not_to be_matching end end @@ -37,7 +37,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with a missing remote key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(remote_file).not_to be_matching end end @@ -49,7 +49,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_return(body: remote_file_content) end - it 'should return true' do + it 'returns true' do expect(remote_file.valid?).to be_truthy end end @@ -57,7 +57,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with an irregular url' do let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' } - it 'should return false' do + it 'returns false' do expect(remote_file.valid?).to be_falsy end end @@ -67,7 +67,7 @@ describe Gitlab::Ci::Config::External::File::Remote do allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error) end - it 'should be falsy' do + it 'is falsy' do expect(remote_file.valid?).to be_falsy end end @@ -75,7 +75,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when is not a yaml file' do let(:location) { 'https://asdasdasdaj48ggerexample.com' } - it 'should be falsy' do + it 'is falsy' do expect(remote_file.valid?).to be_falsy end end @@ -83,7 +83,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with an internal url' do let(:location) { 'http://localhost:8080' } - it 'should be falsy' do + it 'is falsy' do expect(remote_file.valid?).to be_falsy end end @@ -95,7 +95,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_return(body: remote_file_content) end - it 'should return the content of the file' do + it 'returns the content of the file' do expect(remote_file.content).to eql(remote_file_content) end end @@ -105,7 +105,7 @@ describe Gitlab::Ci::Config::External::File::Remote do allow(Gitlab::HTTP).to receive(:get).and_raise(Timeout::Error) end - it 'should be falsy' do + it 'is falsy' do expect(remote_file.content).to be_falsy end end @@ -117,7 +117,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error')) end - it 'should be nil' do + it 'is nil' do expect(remote_file.content).to be_nil end end @@ -125,7 +125,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'with an internal url' do let(:location) { 'http://localhost:8080' } - it 'should be nil' do + it 'is nil' do expect(remote_file.content).to be_nil end end @@ -147,7 +147,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_timeout end - it 'should returns error message about a timeout' do + it 'returns error message about a timeout' do expect(subject).to match /could not be fetched because of a timeout error!/ end end @@ -157,7 +157,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error) end - it 'should returns error message about a HTTP error' do + it 'returns error message about a HTTP error' do expect(subject).to match /could not be fetched because of HTTP error!/ end end @@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::External::File::Remote do WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404) end - it 'should returns error message about a timeout' do + it 'returns error message about a timeout' do expect(subject).to match /could not be fetched because of HTTP code `404` error!/ end end @@ -175,7 +175,7 @@ describe Gitlab::Ci::Config::External::File::Remote do context 'when the URL is blocked' do let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' } - it 'should include details about blocked URL' do + it 'includes details about blocked URL' do expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \ 'is blocked: Requests to localhost are not allowed!' end diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb index 8ecaf4800f8..1609b8fd66b 100644 --- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'when a template is specified' do let(:params) { { template: 'some-template' } } - it 'should return true' do + it 'returns true' do expect(template_file).to be_matching end end @@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with a missing template' do let(:params) { { template: nil } } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_matching end end @@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with a missing template key' do let(:params) { {} } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_matching end end @@ -42,7 +42,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'when is a valid template name' do let(:template) { 'Auto-DevOps.gitlab-ci.yml' } - it 'should return true' do + it 'returns true' do expect(template_file).to be_valid end end @@ -50,7 +50,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with invalid template name' do let(:template) { 'Template.yml' } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_valid expect(template_file.error_message).to include('Template file `Template.yml` is not a valid location!') end @@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::External::File::Template do context 'with a non-existing template' do let(:template) { 'I-Do-Not-Have-This-Template.gitlab-ci.yml' } - it 'should return false' do + it 'returns false' do expect(template_file).not_to be_valid expect(template_file.error_message).to include('Included file `I-Do-Not-Have-This-Template.gitlab-ci.yml` is empty or does not exist!') end diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index 3f6f6d7c5d9..e94bb44f990 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Ci::Config::External::Processor do context 'when no external files defined' do let(:values) { { image: 'ruby:2.2' } } - it 'should return the same values' do + it 'returns the same values' do expect(processor.perform).to eq(values) end end @@ -29,7 +29,7 @@ describe Gitlab::Ci::Config::External::Processor do context 'when an invalid local file is defined' do let(:values) { { include: '/lib/gitlab/ci/templates/non-existent-file.yml', image: 'ruby:2.2' } } - it 'should raise an error' do + it 'raises an error' do expect { processor.perform }.to raise_error( described_class::IncludeError, "Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!" @@ -45,7 +45,7 @@ describe Gitlab::Ci::Config::External::Processor do WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error')) end - it 'should raise an error' do + it 'raises an error' do expect { processor.perform }.to raise_error( described_class::IncludeError, "Remote file `#{remote_file}` could not be fetched because of a socket error!" @@ -78,12 +78,12 @@ describe Gitlab::Ci::Config::External::Processor do WebMock.stub_request(:get, remote_file).to_return(body: external_file_content) end - it 'should append the file to the values' do + it 'appends the file to the values' do output = processor.perform expect(output.keys).to match_array([:image, :before_script, :rspec, :rubocop]) end - it "should remove the 'include' keyword" do + it "removes the 'include' keyword" do expect(processor.perform[:include]).to be_nil end end @@ -105,12 +105,12 @@ describe Gitlab::Ci::Config::External::Processor do .to receive(:fetch_local_content).and_return(local_file_content) end - it 'should append the file to the values' do + it 'appends the file to the values' do output = processor.perform expect(output.keys).to match_array([:image, :before_script]) end - it "should remove the 'include' keyword" do + it "removes the 'include' keyword" do expect(processor.perform[:include]).to be_nil end end @@ -148,11 +148,11 @@ describe Gitlab::Ci::Config::External::Processor do WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) end - it 'should append the files to the values' do + it 'appends the files to the values' do expect(processor.perform.keys).to match_array([:image, :stages, :before_script, :rspec]) end - it "should remove the 'include' keyword" do + it "removes the 'include' keyword" do expect(processor.perform[:include]).to be_nil end end @@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::External::Processor do .to receive(:fetch_local_content).and_return(local_file_content) end - it 'should raise an error' do + it 'raises an error' do expect { processor.perform }.to raise_error( described_class::IncludeError, "Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!" @@ -190,7 +190,7 @@ describe Gitlab::Ci::Config::External::Processor do HEREDOC end - it 'should take precedence' do + it 'takes precedence' do WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content) expect(processor.perform[:image]).to eq('ruby:2.2') end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 00b2753c5fc..fd2a29e4ddb 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -225,7 +225,7 @@ describe Gitlab::Ci::Config do end context "when gitlab_ci_yml has valid 'include' defined" do - it 'should return a composed hash' do + it 'returns a composed hash' do before_script_values = [ "apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs", "ruby -v", "which ruby", @@ -316,7 +316,7 @@ describe Gitlab::Ci::Config do HEREDOC end - it 'should take precedence' do + it 'takes precedence' do expect(config.to_hash).to eq({ image: 'ruby:2.2' }) end end @@ -341,7 +341,7 @@ describe Gitlab::Ci::Config do HEREDOC end - it 'should merge the variables dictionaries' do + it 'merges the variables dictionaries' do expect(config.to_hash).to eq({ variables: { A: 'alpha', B: 'beta', C: 'gamma', D: 'delta' } }) end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb index dc13cae961c..c7f4fc98ca3 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do step.perform! end - it 'should break the chain' do + it 'breaks the chain' do expect(step.break?).to be true end @@ -37,11 +37,11 @@ describe Gitlab::Ci::Pipeline::Chain::Skip do step.perform! end - it 'should not break the chain' do + it 'does not break the chain' do expect(step.break?).to be false end - it 'should not skip a pipeline chain' do + it 'does not skip a pipeline chain' do expect(pipeline.reload).not_to be_skipped end end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb index b379b08ad62..b6231510b91 100644 --- a/spec/lib/gitlab/ci/status/build/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -123,6 +123,35 @@ describe Gitlab::Ci::Status::Build::Factory do expect(status.action_path).to include 'retry' end end + + context 'when build has unmet prerequisites' do + let(:build) { create(:ci_build, :prerequisite_failure) } + + it 'matches correct core status' do + expect(factory.core_status).to be_a Gitlab::Ci::Status::Failed + end + + it 'matches correct extended statuses' do + expect(factory.extended_statuses) + .to eq [Gitlab::Ci::Status::Build::Retryable, + Gitlab::Ci::Status::Build::FailedUnmetPrerequisites] + end + + it 'fabricates a failed with unmet prerequisites build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::FailedUnmetPrerequisites + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'status_failed' + expect(status.favicon).to eq 'favicon_status_failed' + expect(status.label).to eq 'failed' + expect(status).to have_details + expect(status).to have_action + expect(status.action_title).to include 'Retry' + expect(status.action_path).to include 'retry' + end + end end context 'when build is a canceled' do diff --git a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb new file mode 100644 index 00000000000..a4854bdc6b9 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Status::Build::FailedUnmetPrerequisites do + describe '#illustration' do + subject { described_class.new(double).illustration } + + it { is_expected.to include(:image, :size, :title, :content) } + end + + describe '.matches?' do + let(:build) { create(:ci_build, :created) } + + subject { described_class.matches?(build, double) } + + context 'when build has not failed' do + it { is_expected.to be_falsey } + end + + context 'when build has failed' do + before do + build.drop!(failure_reason) + end + + context 'with unmet prerequisites' do + let(:failure_reason) { :unmet_prerequisites } + + it { is_expected.to be_truthy } + end + + context 'with a different error' do + let(:failure_reason) { :runner_system_failure } + + it { is_expected.to be_falsey } + end + end + end +end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 63a0d54dcfc..8b39c4e4dd0 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -615,6 +615,14 @@ module Gitlab subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts) } context "when validating a ci config file with no project context" do + context "when a single string is provided" do + let(:include_content) { "/local.gitlab-ci.yml" } + + it "does not return any error" do + expect { subject }.not_to raise_error + end + end + context "when an array is provided" do let(:include_content) { ["/local.gitlab-ci.yml"] } diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index b7924302014..51e5bdc6307 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -150,13 +150,13 @@ describe Gitlab::ContributionsCalendar do end describe '#starting_year' do - it "should be the start of last year" do + it "is the start of last year" do expect(calendar.starting_year).to eq(last_year.year) end end describe '#starting_month' do - it "should be the start of this month" do + it "is the start of this month" do expect(calendar.starting_month).to eq(today.month) end end diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb index 1d31f96159c..ddd54a669a3 100644 --- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb +++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do describe '#rename_wildcard_paths' do it_behaves_like 'renames child namespaces' - it 'should rename projects' do + it 'renames projects' do rename_projects = double expect(described_class::RenameProjects) .to receive(:new).with(['the-path'], subject) @@ -40,7 +40,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do end describe '#rename_root_paths' do - it 'should rename namespaces' do + it 'renames namespaces' do rename_namespaces = double expect(described_class::RenameNamespaces) .to receive(:new).with(['the-path'], subject) diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index 256166dbad3..0697594c725 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do let(:diffable) { merge_request.merge_request_diff } end - it 'it uses a different cache key if diff line keys change' do + it 'uses a different cache key if diff line keys change' do mr_diff = described_class.new(merge_request.merge_request_diff, diff_options: nil) key = mr_diff.cache_key diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb index f69cb502ca6..a7cb0bb2a87 100644 --- a/spec/lib/gitlab/etag_caching/router_spec.rb +++ b/spec/lib/gitlab/etag_caching/router_spec.rb @@ -19,6 +19,24 @@ describe Gitlab::EtagCaching::Router do expect(result.name).to eq 'issue_title' end + it 'matches with a project name that includes a suffix of create' do + result = described_class.match( + '/group/test-create/issues/123/realtime_changes' + ) + + expect(result).to be_present + expect(result.name).to eq 'issue_title' + end + + it 'matches with a project name that includes a prefix of create' do + result = described_class.match( + '/group/create-test/issues/123/realtime_changes' + ) + + expect(result).to be_present + expect(result.name).to eq 'issue_title' + end + it 'matches project pipelines endpoint' do result = described_class.match( '/my-group/my-project/pipelines.json' diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 4a4ac833e39..507bf222810 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -113,13 +113,13 @@ describe Gitlab::Git::Commit, :seed_helper do context 'Class methods' do shared_examples '.find' do - it "should return first head commit if without params" do + it "returns first head commit if without params" do expect(described_class.last(repository).id).to eq( rugged_repo.head.target.oid ) end - it "should return valid commit" do + it "returns valid commit" do expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_valid_commit end @@ -127,21 +127,21 @@ describe Gitlab::Git::Commit, :seed_helper do expect(described_class.find(repository, SeedRepo::Commit::ID).parent_ids).to be_an(Array) end - it "should return valid commit for tag" do + it "returns valid commit for tag" do expect(described_class.find(repository, 'v1.0.0').id).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') end - it "should return nil for non-commit ids" do + it "returns nil for non-commit ids" do blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") expect(described_class.find(repository, blob.id)).to be_nil end - it "should return nil for parent of non-commit object" do + it "returns nil for parent of non-commit object" do blob = Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") expect(described_class.find(repository, "#{blob.id}^")).to be_nil end - it "should return nil for nonexisting ids" do + it "returns nil for nonexisting ids" do expect(described_class.find(repository, "+123_4532530XYZ")).to be_nil end @@ -328,7 +328,7 @@ describe Gitlab::Git::Commit, :seed_helper do end describe '.find_all' do - it 'should return a return a collection of commits' do + it 'returns a return a collection of commits' do commits = described_class.find_all(repository) expect(commits).to all( be_a_kind_of(described_class) ) diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 1d22329b670..9ab669ad488 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -182,7 +182,7 @@ EOT context "without default options" do let(:filtered_options) { described_class.filter_diff_options(options) } - it "should filter invalid options" do + it "filters invalid options" do expect(filtered_options).not_to have_key(:invalid_opt) end end @@ -193,16 +193,16 @@ EOT described_class.filter_diff_options(options, default_options) end - it "should filter invalid options" do + it "filters invalid options" do expect(filtered_options).not_to have_key(:invalid_opt) expect(filtered_options).not_to have_key(:bad_opt) end - it "should merge with default options" do + it "merges with default options" do expect(filtered_options).to have_key(:ignore_whitespace_change) end - it "should override default options" do + it "overrides default options" do expect(filtered_options).to have_key(:max_files) expect(filtered_options[:max_files]).to eq(100) end diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb index 6fd2b33486b..de81dcd227d 100644 --- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb +++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::GitmodulesParser do - it 'should parse a .gitmodules file correctly' do + it 'parses a .gitmodules file correctly' do data = <<~GITMODULES [submodule "vendor/libgit2"] path = vendor/libgit2 diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index fc8f590068a..fdb43d1221a 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -450,20 +450,20 @@ describe Gitlab::Git::Repository, :seed_helper do ensure_seeds end - it "should create a new branch" do + it "creates a new branch" do expect(repository.create_branch('new_branch', 'master')).not_to be_nil end - it "should create a new branch with the right name" do + it "creates a new branch with the right name" do expect(repository.create_branch('another_branch', 'master').name).to eq('another_branch') end - it "should fail if we create an existing branch" do + it "fails if we create an existing branch" do repository.create_branch('duplicated_branch', 'master') expect {repository.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists") end - it "should fail if we create a branch from a non existing ref" do + it "fails if we create a branch from a non existing ref" do expect {repository.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge") end end @@ -522,7 +522,7 @@ describe Gitlab::Git::Repository, :seed_helper do describe "#refs_hash" do subject { repository.refs_hash } - it "should have as many entries as branches and tags" do + it "has as many entries as branches and tags" do expected_refs = SeedRepo::Repo::BRANCHES + SeedRepo::Repo::TAGS # We flatten in case a commit is pointed at by more than one branch and/or tag expect(subject.values.flatten.size).to eq(expected_refs.size) @@ -613,11 +613,11 @@ describe Gitlab::Git::Repository, :seed_helper do end shared_examples 'search files by content' do - it 'should have 2 items' do + it 'has 2 items' do expect(search_results.size).to eq(2) end - it 'should have the correct matching line' do + it 'has the correct matching line' do expect(search_results).to contain_exactly("search-files-by-content-branch:encoding/CHANGELOG\u00001\u0000search-files-by-content change\n", "search-files-by-content-branch:anotherfile\u00001\u0000search-files-by-content change\n") end @@ -850,7 +850,7 @@ describe Gitlab::Git::Repository, :seed_helper do context "where provides 'after' timestamp" do options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } - it "should returns commits on or after that timestamp" do + it "returns commits on or after that timestamp" do commits = repository.log(options) expect(commits.size).to be > 0 @@ -863,7 +863,7 @@ describe Gitlab::Git::Repository, :seed_helper do context "where provides 'before' timestamp" do options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') } - it "should returns commits on or before that timestamp" do + it "returns commits on or before that timestamp" do commits = repository.log(options) expect(commits.size).to be > 0 @@ -1064,14 +1064,14 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#find_branch' do - it 'should return a Branch for master' do + it 'returns a Branch for master' do branch = repository.find_branch('master') expect(branch).to be_a_kind_of(Gitlab::Git::Branch) expect(branch.name).to eq('master') end - it 'should handle non-existent branch' do + it 'handles non-existent branch' do branch = repository.find_branch('this-is-garbage') expect(branch).to eq(nil) diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 41810a8ec03..705df1f4fe7 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -197,6 +197,11 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do .to receive(:fetch_as_mirror) .with(project.import_url, refmap: Gitlab::GithubImport.refmap, forced: true, remote_name: 'github') + service = double + expect(Projects::HousekeepingService) + .to receive(:new).with(project, :gc).and_return(service) + expect(service).to receive(:execute) + expect(importer.import_repository).to eq(true) end diff --git a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb index 4a669408025..e1106f7496a 100644 --- a/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb +++ b/spec/lib/gitlab/kubernetes/cluster_role_binding_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::Kubernetes::ClusterRoleBinding do subject { cluster_role_binding.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/config_map_spec.rb b/spec/lib/gitlab/kubernetes/config_map_spec.rb index fe65d03875f..911d6024804 100644 --- a/spec/lib/gitlab/kubernetes/config_map_spec.rb +++ b/spec/lib/gitlab/kubernetes/config_map_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Kubernetes::ConfigMap do let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: application.files) } subject { config_map.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb index aacae78be43..78a4eb44e38 100644 --- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb @@ -41,7 +41,7 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do describe '#pod_resource' do subject { base_command.pod_resource } - it 'should returns a kubeclient resoure with pod content for application' do + it 'returns a kubeclient resoure with pod content for application' do is_expected.to be_an_instance_of ::Kubeclient::Resource end diff --git a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb index 167bee22fc3..04649353976 100644 --- a/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/certificate_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Kubernetes::Helm::Certificate do describe '.generate_root' do subject { described_class.generate_root } - it 'should generate a root CA that expires a long way in the future' do + it 'generates a root CA that expires a long way in the future' do expect(subject.cert.not_after).to be > 999.years.from_now end end @@ -13,14 +13,14 @@ describe Gitlab::Kubernetes::Helm::Certificate do describe '#issue' do subject { described_class.generate_root.issue } - it 'should generate a cert that expires soon' do + it 'generates a cert that expires soon' do expect(subject.cert.not_after).to be < 60.minutes.from_now end context 'passing in INFINITE_EXPIRY' do subject { described_class.generate_root.issue(expires_in: described_class::INFINITE_EXPIRY) } - it 'should generate a cert that expires a long way in the future' do + it 'generates a cert that expires a long way in the future' do expect(subject.cert.not_after).to be > 999.years.from_now end end diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 95b6b3fd953..06c8d127951 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -10,11 +10,11 @@ describe Gitlab::Kubernetes::Helm::Pod do subject { described_class.new(command, namespace, service_account_name: service_account_name) } context 'with a command' do - it 'should generate a Kubeclient::Resource' do + it 'generates a Kubeclient::Resource' do expect(subject.generate).to be_a_kind_of(Kubeclient::Resource) end - it 'should generate the appropriate metadata' do + it 'generates the appropriate metadata' do metadata = subject.generate.metadata expect(metadata.name).to eq("install-#{app.name}") expect(metadata.namespace).to eq('gitlab-managed-apps') @@ -22,12 +22,12 @@ describe Gitlab::Kubernetes::Helm::Pod do expect(metadata.labels['gitlab.org/application']).to eq(app.name) end - it 'should generate a container spec' do + it 'generates a container spec' do spec = subject.generate.spec expect(spec.containers.count).to eq(1) end - it 'should generate the appropriate specifications for the container' do + it 'generates the appropriate specifications for the container' do container = subject.generate.spec.containers.first expect(container.name).to eq('helm') expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7') @@ -37,30 +37,30 @@ describe Gitlab::Kubernetes::Helm::Pod do expect(container.args).to match_array(["-c", "$(COMMAND_SCRIPT)"]) end - it 'should include a never restart policy' do + it 'includes a never restart policy' do spec = subject.generate.spec expect(spec.restartPolicy).to eq('Never') end - it 'should include volumes for the container' do + it 'includes volumes for the container' do container = subject.generate.spec.containers.first expect(container.volumeMounts.first['name']).to eq('configuration-volume') expect(container.volumeMounts.first['mountPath']).to eq("/data/helm/#{app.name}/config") end - it 'should include a volume inside the specification' do + it 'includes a volume inside the specification' do spec = subject.generate.spec expect(spec.volumes.first['name']).to eq('configuration-volume') end - it 'should mount configMap specification in the volume' do + it 'mounts configMap specification in the volume' do volume = subject.generate.spec.volumes.first expect(volume.configMap['name']).to eq("values-content-configuration-#{app.name}") expect(volume.configMap['items'].first['key']).to eq(:'values.yaml') expect(volume.configMap['items'].first['path']).to eq(:'values.yaml') end - it 'should have no serviceAccountName' do + it 'has no serviceAccountName' do spec = subject.generate.spec expect(spec.serviceAccountName).to be_nil end @@ -68,7 +68,7 @@ describe Gitlab::Kubernetes::Helm::Pod do context 'with a service_account_name' do let(:service_account_name) { 'sa' } - it 'should use the serviceAccountName provided' do + it 'uses the serviceAccountName provided' do spec = subject.generate.spec expect(spec.serviceAccountName).to eq(service_account_name) end diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb index a1a59533bfb..50acee254cb 100644 --- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb +++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb @@ -42,7 +42,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do ).generate end - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/service_account_spec.rb b/spec/lib/gitlab/kubernetes/service_account_spec.rb index 8da9e932dc3..0d525966d18 100644 --- a/spec/lib/gitlab/kubernetes/service_account_spec.rb +++ b/spec/lib/gitlab/kubernetes/service_account_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Kubernetes::ServiceAccount do subject { service_account.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb index 0773d3d9aec..0d334bed45f 100644 --- a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb +++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::Kubernetes::ServiceAccountToken do subject { service_account_token.generate } - it 'should build a Kubeclient Resource' do + it 'builds a Kubeclient Resource' do is_expected.to eq(resource) end end diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb index 4700a7ad2e1..e6e9ae3223e 100644 --- a/spec/lib/gitlab/object_hierarchy_spec.rb +++ b/spec/lib/gitlab/object_hierarchy_spec.rb @@ -81,6 +81,24 @@ describe Gitlab::ObjectHierarchy, :postgresql do expect { relation.update_all(share_with_group_lock: false) } .to raise_error(ActiveRecord::ReadOnlyRecord) end + + context 'when with_depth is true' do + let(:relation) do + described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true) + end + + it 'includes depth in the results' do + object_depths = { + parent.id => 1, + child1.id => 2, + child2.id => 3 + } + + relation.each do |object| + expect(object.depth).to eq(object_depths[object.id]) + end + end + end end describe '#descendants' do @@ -91,6 +109,28 @@ describe Gitlab::ObjectHierarchy, :postgresql do end end + describe '#max_descendants_depth' do + subject { described_class.new(base_relation).max_descendants_depth } + + context 'when base relation is empty' do + let(:base_relation) { Group.where(id: nil) } + + it { expect(subject).to be_nil } + end + + context 'when base has no children' do + let(:base_relation) { Group.where(id: child2) } + + it { expect(subject).to eq(1) } + end + + context 'when base has grandchildren' do + let(:base_relation) { Group.where(id: parent) } + + it { expect(subject).to eq(3) } + end + end + describe '#ancestors' do it 'includes only the ancestors' do relation = described_class.new(Group.where(id: child2)).ancestors diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb new file mode 100644 index 00000000000..7f6283715f2 --- /dev/null +++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do + include PrometheusHelpers + + let(:project) { create(:project) } + let(:serverless_func) { Serverless::Function.new(project, 'test-name', 'test-ns') } + + let(:client) { double('prometheus_client') } + subject { described_class.new(client) } + + context 'verify queries' do + before do + allow(PrometheusMetric).to receive(:find_by_identifier).and_return(create(:prometheus_metric, query: prometheus_istio_query('test-name', 'test-ns'))) + allow(client).to receive(:query_range) + end + + it 'has the query, but no data' do + results = subject.query(serverless_func.id) + + expect(results.queries[0][:query_range]).to eql('floor(sum(rate(istio_revision_request_count{destination_configuration="test-name", destination_namespace="test-ns"}[1m])*30))') + end + end +end diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb index 2517ee71f24..f15ae83a02c 100644 --- a/spec/lib/gitlab/prometheus_client_spec.rb +++ b/spec/lib/gitlab/prometheus_client_spec.rb @@ -60,15 +60,13 @@ describe Gitlab::PrometheusClient do end describe 'failure to reach a provided prometheus url' do - let(:prometheus_url) {"https://prometheus.invalid.example.com"} + let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"} - subject { described_class.new(RestClient::Resource.new(prometheus_url)) } - - context 'exceptions are raised' do + shared_examples 'exceptions are raised' do it 'raises a Gitlab::PrometheusClient::Error error when a SocketError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Can't connect to #{prometheus_url}") expect(req_stub).to have_been_requested end @@ -76,7 +74,7 @@ describe Gitlab::PrometheusClient do it 'raises a Gitlab::PrometheusClient::Error error when a SSLError is rescued' do req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "#{prometheus_url} contains invalid SSL data") expect(req_stub).to have_been_requested end @@ -84,11 +82,23 @@ describe Gitlab::PrometheusClient do 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) - expect { subject.send(:get, '/', {}) } + expect { subject } .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error") expect(req_stub).to have_been_requested end end + + context 'ping' do + subject { described_class.new(RestClient::Resource.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' }) } + + it_behaves_like 'exceptions are raised' + end end describe '#query' do @@ -258,4 +268,59 @@ describe Gitlab::PrometheusClient do it { is_expected.to eq(step) } end end + + describe 'proxy' do + context 'get API' do + let(:prometheus_query) { prometheus_cpu_query('env-slug') } + let(:query_url) { prometheus_query_url(prometheus_query) } + + around do |example| + Timecop.freeze { example.run } + end + + context 'when response status code is 200' do + it 'returns response object' do + req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector')) + + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(response.code).to eq(200) + expect(json_response).to eq({ + 'status' => 'success', + 'data' => { + 'resultType' => 'vector', + 'result' => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] + } + }) + expect(req_stub).to have_been_requested + end + end + + context 'when response status code is not 200' do + it 'returns response object' do + req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'error' }) + + response = subject.proxy('query', { query: prometheus_query }) + json_response = JSON.parse(response.body) + + expect(req_stub).to have_been_requested + expect(response.code).to eq(400) + expect(json_response).to eq('error' => 'error') + end + end + + context 'when RestClient::Exception is raised' do + before do + stub_prometheus_request_with_exception(query_url, RestClient::Exception) + end + + it 'raises PrometheusClient::Error' do + expect { subject.proxy('query', { query: prometheus_query }) }.to( + raise_error(Gitlab::PrometheusClient::Error, 'Network connection error') + ) + end + end + end + end end diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 4c7ca4e2b57..8fbda929064 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -44,8 +44,10 @@ describe ::Gitlab::RepoPath do end end - it "returns nil for non existent paths" do - expect(described_class.parse("path/non-existent.git")).to eq(nil) + it "returns the default type for non existent paths" do + _project, type, _redirected = described_class.parse("path/non-existent.git") + + expect(type).to eq(Gitlab::GlRepository.default_type) end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 4b57eecff93..312aa3be490 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -97,7 +97,7 @@ describe Gitlab::SearchResults do results.objects('merge_requests') end - it 'it skips project filter if default project context is used' do + it 'skips project filter if default project context is used' do allow(results).to receive(:default_project_filter).and_return(true) expect(results).not_to receive(:project_ids_relation) @@ -113,7 +113,7 @@ describe Gitlab::SearchResults do results.objects('issues') end - it 'it skips project filter if default project context is used' do + it 'skips project filter if default project context is used' do allow(results).to receive(:default_project_filter).and_return(true) expect(results).not_to receive(:project_ids_relation) diff --git a/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb index c9d1a06b3e6..0bbaf5968ed 100644 --- a/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb +++ b/spec/lib/gitlab/tracing/rails/action_view_subscriber_spec.rb @@ -7,19 +7,19 @@ describe Gitlab::Tracing::Rails::ActionViewSubscriber do using RSpec::Parameterized::TableSyntax shared_examples 'an actionview notification' do - it 'should notify the tracer when the hash contains null values' do + it 'notifies the tracer when the hash contains null values' do expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception) subject.public_send(notify_method, start, finish, payload) end - it 'should notify the tracer when the payload is missing values' do + it 'notifies the tracer when the payload is missing values' do expect(subject).to receive(:postnotify_span).with(notification_name, start, finish, tags: expected_tags, exception: exception) subject.public_send(notify_method, start, finish, payload.compact) end - it 'should not throw exceptions when with the default tracer' do + it 'does not throw exceptions when with the default tracer' do expect { subject.public_send(notify_method, start, finish, payload) }.not_to raise_error end end diff --git a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb index 3d066843148..7bd0875fa68 100644 --- a/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb +++ b/spec/lib/gitlab/tracing/rails/active_record_subscriber_spec.rb @@ -53,19 +53,19 @@ describe Gitlab::Tracing::Rails::ActiveRecordSubscriber do } end - it 'should notify the tracer when the hash contains null values' do + it 'notifies the tracer when the hash contains null values' do expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception) subject.notify(start, finish, payload) end - it 'should notify the tracer when the payload is missing values' do + it 'notifies the tracer when the payload is missing values' do expect(subject).to receive(:postnotify_span).with(operation_name, start, finish, tags: expected_tags, exception: exception) subject.notify(start, finish, payload.compact) end - it 'should not throw exceptions when with the default tracer' do + it 'does not throw exceptions when with the default tracer' do expect { subject.notify(start, finish, payload) }.not_to raise_error end end diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb index 566b5050e47..db75ce2a998 100644 --- a/spec/lib/gitlab/tracing_spec.rb +++ b/spec/lib/gitlab/tracing_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Tracing do end with_them do - it 'should return the correct state for .enabled?' do + it 'returns the correct state for .enabled?' do expect(described_class).to receive(:connection_string).and_return(connection_string) expect(described_class.enabled?).to eq(enabled_state) @@ -33,7 +33,7 @@ describe Gitlab::Tracing do end with_them do - it 'should return the correct state for .tracing_url_enabled?' do + it 'returns the correct state for .tracing_url_enabled?' do expect(described_class).to receive(:enabled?).and_return(enabled?) allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template) @@ -56,7 +56,7 @@ describe Gitlab::Tracing do end with_them do - it 'should return the correct state for .tracing_url' do + it 'returns the correct state for .tracing_url' do expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?) allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template) allow(Gitlab::CorrelationId).to receive(:current_id).and_return(correlation_id) diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb index 6e98a999766..5861e6955a6 100644 --- a/spec/lib/gitlab/url_sanitizer_spec.rb +++ b/spec/lib/gitlab/url_sanitizer_spec.rb @@ -161,7 +161,7 @@ describe Gitlab::UrlSanitizer do end context 'when credentials contains special chars' do - it 'should parse the URL without errors' do + it 'parses the URL without errors' do url_sanitizer = described_class.new("https://foo:b?r@github.com/me/project.git") expect(url_sanitizer.sanitized_url).to eq("https://github.com/me/project.git") diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 3333f8307ae..cb14204b99a 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -61,13 +61,37 @@ describe Sentry::Client do end end + shared_examples 'maps exceptions' do + exceptions = { + HTTParty::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', + Errno::ECONNREFUSED => 'Connection refused', + StandardError => 'Sentry request failed due to StandardError' + } + + exceptions.each do |exception, message| + context "#{exception}" do + before do + stub_request(:get, sentry_request_url).to_raise(exception) + end + + it do + expect { subject } + .to raise_exception(Sentry::Client::Error, message) + end + end + end + end + describe '#list_issues' do let(:issue_status) { 'unresolved' } let(:limit) { 20 } - let(:sentry_api_response) { issues_sample_response } + let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } - let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: sentry_api_response) } + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } subject { client.list_issues(issue_status: issue_status, limit: limit) } @@ -121,16 +145,14 @@ describe Sentry::Client do # Sentry API returns 404 if there are extra slashes in the URL! context 'extra slashes in URL' do let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' } - let(:client) { described_class.new(sentry_url, token) } - let!(:valid_req_stub) do - stub_sentry_request( - 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ + let(:sentry_request_url) do + 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ 'issues/?limit=20&query=is:unresolved' - ) end it 'removes extra slashes in api url' do + expect(client.url).to eq(sentry_url) expect(Gitlab::HTTP).to receive(:get).with( URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'), anything @@ -138,7 +160,7 @@ describe Sentry::Client do subject - expect(valid_req_stub).to have_been_requested + expect(sentry_api_request).to have_been_requested end end @@ -169,6 +191,8 @@ describe Sentry::Client do expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') end end + + it_behaves_like 'maps exceptions' end describe '#list_projects' do @@ -260,12 +284,18 @@ describe Sentry::Client do expect(valid_req_stub).to have_been_requested end end + + context 'when exception is raised' do + let(:sentry_request_url) { sentry_list_projects_url } + + it_behaves_like 'maps exceptions' + end end private def stub_sentry_request(url, body: {}, status: 200, headers: {}) - WebMock.stub_request(:get, url) + stub_request(:get, url) .to_return( status: status, headers: { 'Content-Type' => 'application/json' }.merge(headers), diff --git a/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb new file mode 100644 index 00000000000..572b7dfd0c8 --- /dev/null +++ b/spec/migrations/clean_up_noteable_id_for_notes_on_commits_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190313092516_clean_up_noteable_id_for_notes_on_commits.rb') + +describe CleanUpNoteableIdForNotesOnCommits, :migration do + let(:notes) { table(:notes) } + + before do + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + notes.create!(noteable_type: 'Commit', commit_id: '3d0a182204cece4857f81c6462720e0ad1af39c9', noteable_id: 3, note: 'Test') + + notes.create!(noteable_type: 'Issue', noteable_id: 1, note: 'Test') + notes.create!(noteable_type: 'MergeRequest', noteable_id: 1, note: 'Test') + notes.create!(noteable_type: 'Snippet', noteable_id: 1, note: 'Test') + end + + it 'clears noteable_id for notes on commits' do + expect { migrate! }.to change { dirty_notes_on_commits.count }.from(3).to(0) + end + + it 'does not clear noteable_id for other notes' do + expect { migrate! }.not_to change { other_notes.count } + end + + def dirty_notes_on_commits + notes.where(noteable_type: 'Commit').where('noteable_id IS NOT NULL') + end + + def other_notes + notes.where("noteable_type != 'Commit' AND noteable_id IS NOT NULL") + end +end diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb index b1ff3cfd355..349cffea70e 100644 --- a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb +++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb @@ -25,7 +25,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do context 'with ProjectAutoDevOps with no domain' do let(:domain) { nil } - it 'should not update cluster project' do + it 'does not update cluster project' do migrate! expect(clusters_without_domain.count).to eq(clusters_table.count) @@ -35,7 +35,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do context 'with ProjectAutoDevOps with domain' do let(:domain) { 'example-domain.com' } - it 'should update all cluster projects' do + it 'updates all cluster projects' do migrate! expect(clusters_with_domain.count).to eq(clusters_table.count) @@ -49,7 +49,7 @@ describe MigrateAutoDevOpsDomainToClusterDomain, :migration do setup_cluster_projects_with_domain(quantity: 25, domain: nil) end - it 'should only update specific cluster projects' do + it 'only updates specific cluster projects' do migrate! expect(clusters_with_domain.count).to eq(20) diff --git a/spec/migrations/truncate_user_fullname_spec.rb b/spec/migrations/truncate_user_fullname_spec.rb new file mode 100644 index 00000000000..17fd4d9f688 --- /dev/null +++ b/spec/migrations/truncate_user_fullname_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190325080727_truncate_user_fullname.rb') + +describe TruncateUserFullname, :migration do + let(:users) { table(:users) } + + let(:user_short) { create_user(name: 'abc', email: 'test_short@example.com') } + let(:user_long) { create_user(name: 'a' * 200 + 'z', email: 'test_long@example.com') } + + def create_user(params) + users.create!(params.merge(projects_limit: 0)) + end + + it 'truncates user full name to the first 128 characters' do + expect { migrate! }.to change { user_long.reload.name }.to('a' * 128) + end + + it 'does not truncate short names' do + expect { migrate! }.not_to change { user_short.reload.name.length } + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c5579dafb4a..c81572d739e 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -6,6 +6,7 @@ describe ApplicationSetting do let(:setting) { described_class.create_from_defaults } it { include(CacheableAttributes) } + it { include(ApplicationSettingImplementation) } it { expect(described_class.current_without_cache).to eq(described_class.last) } it { expect(setting).to be_valid } @@ -286,12 +287,10 @@ describe ApplicationSetting do end context 'restrict creating duplicates' do - before do - described_class.create_from_defaults - end + let!(:current_settings) { described_class.create_from_defaults } - it 'raises an record creation violation if already created' do - expect { described_class.create_from_defaults }.to raise_error(ActiveRecord::RecordNotUnique) + it 'returns the current settings' do + expect(described_class.create_from_defaults).to eq(current_settings) end end diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb index 314d7d1e9f4..c661f5384ea 100644 --- a/spec/models/badge_spec.rb +++ b/spec/models/badge_spec.rb @@ -61,7 +61,7 @@ describe Badge do end shared_examples 'rendered_links' do - it 'should use the project information to populate the url placeholders' do + it 'uses the project information to populate the url placeholders' do stub_project_commit_info(project) expect(badge.public_send("rendered_#{method}", project)).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever" diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb index e683d110252..d41c5cf2ca1 100644 --- a/spec/models/badges/project_badge_spec.rb +++ b/spec/models/badges/project_badge_spec.rb @@ -14,7 +14,7 @@ describe ProjectBadge do end shared_examples 'rendered_links' do - it 'should use the badge project information to populate the url placeholders' do + it 'uses the badge project information to populate the url placeholders' do stub_project_commit_info(project) expect(badge.public_send("rendered_#{method}")).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever" diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 2c41dfa65cd..1352a2de2d7 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -117,6 +117,16 @@ describe Ci::Build do it 'returns the job' do is_expected.to include(job) end + + context 'when ci_enable_legacy_artifacts feature flag is disabled' do + before do + stub_feature_flags(ci_enable_legacy_artifacts: false) + end + + it 'does not return the job' do + is_expected.not_to include(job) + end + end end context 'when job has a job artifact archive' do @@ -471,6 +481,14 @@ describe Ci::Build do let(:build) { create(:ci_build, :legacy_artifacts) } it { is_expected.to be_truthy } + + context 'when ci_enable_legacy_artifacts feature flag is disabled' do + before do + stub_feature_flags(ci_enable_legacy_artifacts: false) + end + + it { is_expected.to be_falsy } + end end end end @@ -2708,13 +2726,13 @@ describe Ci::Build do project.deploy_tokens << deploy_token end - it 'should include deploy token variables' do + it 'includes deploy token variables' do is_expected.to include(*deploy_token_variables) end end context 'when gitlab-deploy-token does not exist' do - it 'should not include deploy token variables' do + it 'does not include deploy token variables' do expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil end @@ -3198,7 +3216,7 @@ describe Ci::Build do it 'does not try to create a todo' do project.add_developer(user) - expect(service).not_to receive(:commit_status_merge_requests) + expect(service).not_to receive(:pipeline_merge_requests) subject.drop! end @@ -3234,7 +3252,23 @@ describe Ci::Build do end context 'when build is not configured to be retried' do - subject { create(:ci_build, :running, project: project, user: user) } + subject { create(:ci_build, :running, project: project, user: user, pipeline: pipeline) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + ref: 'feature', + sha: merge_request.diff_head_sha, + merge_requests_as_head_pipeline: [merge_request]) + end + + let(:merge_request) do + create(:merge_request, :opened, + source_branch: 'feature', + source_project: project, + target_branch: 'master', + target_project: project) + end it 'does not retry build' do expect(described_class).not_to receive(:retry) @@ -3253,7 +3287,10 @@ describe Ci::Build do it 'creates a todo' do project.add_developer(user) - expect(service).to receive(:commit_status_merge_requests) + expect_next_instance_of(TodoService) do |todo_service| + expect(todo_service) + .to receive(:merge_request_build_failed).with(merge_request) + end subject.drop! end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index b3ab63925dd..2cb581696a0 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -72,7 +72,7 @@ describe Ci::Runner do expect(instance_runner.errors.full_messages).to include('Runner cannot have projects assigned') end - it 'should fail to save a group assigned to a project runner even if the runner is already saved' do + it 'fails to save a group assigned to a project runner even if the runner is already saved' do group_runner expect { create(:group, runners: [project_runner]) } diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index af7eadfc74c..5cd80edb3a1 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -36,7 +36,7 @@ describe Clusters::Applications::CertManager do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with cert_manager arguments' do + it 'is initialized with cert_manager arguments' do expect(subject.name).to eq('certmanager') expect(subject.chart).to eq('stable/cert-manager') expect(subject.version).to eq('v0.5.2') @@ -52,7 +52,7 @@ describe Clusters::Applications::CertManager do cert_manager.email = cert_email end - it 'should use his/her email to register issuer with certificate provider' do + it 'uses his/her email to register issuer with certificate provider' do expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) end end @@ -68,7 +68,7 @@ describe Clusters::Applications::CertManager do context 'application failed to install previously' do let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('v0.5.2') end end @@ -80,7 +80,7 @@ describe Clusters::Applications::CertManager do subject { application.files } - it 'should include cert_manager specific keys in the values.yaml file' do + it 'includes cert_manager specific keys in the values.yaml file' do expect(values).to include('ingressShim') end end diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index f97d126d918..f177d493a2e 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -36,11 +36,11 @@ describe Clusters::Applications::Helm do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InitCommand) } - it 'should be initialized with 1 arguments' do + it 'is initialized with 1 arguments' do expect(subject.name).to eq('helm') end - it 'should have cert files' do + it 'has cert files' do expect(subject.files[:'ca.pem']).to be_present expect(subject.files[:'ca.pem']).to eq(helm.ca_cert) diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 09e60b9a206..113d29b5551 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -73,7 +73,7 @@ describe Clusters::Applications::Ingress do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with ingress arguments' do + it 'is initialized with ingress arguments' do expect(subject.name).to eq('ingress') expect(subject.chart).to eq('stable/nginx-ingress') expect(subject.version).to eq('1.1.2') @@ -92,7 +92,7 @@ describe Clusters::Applications::Ingress do context 'application failed to install previously' do let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('1.1.2') end end @@ -104,7 +104,7 @@ describe Clusters::Applications::Ingress do subject { application.files } - it 'should include ingress valid keys in values' do + it 'includes ingress valid keys in values' do expect(values).to include('image') expect(values).to include('repository') expect(values).to include('stats') diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 5970a1959b5..1a7363b64f9 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -45,7 +45,7 @@ describe Clusters::Applications::Jupyter do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with 4 arguments' do + it 'is initialized with 4 arguments' do expect(subject.name).to eq('jupyter') expect(subject.chart).to eq('jupyter/jupyterhub') expect(subject.version).to eq('0.9-174bbd5') @@ -65,7 +65,7 @@ describe Clusters::Applications::Jupyter do context 'application failed to install previously' do let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('0.9-174bbd5') end end @@ -77,7 +77,7 @@ describe Clusters::Applications::Jupyter do subject { application.files } - it 'should include valid values' do + it 'includes valid values' do expect(values).to include('ingress') expect(values).to include('hub') expect(values).to include('rbac') diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 25493689fbc..5e68f2634da 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -77,17 +77,17 @@ describe Clusters::Applications::Knative do end shared_examples 'a command' do - it 'should be an instance of Helm::InstallCommand' do + it 'is an instance of Helm::InstallCommand' do expect(subject).to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) end - it 'should be initialized with knative arguments' do + it 'is initialized with knative arguments' do expect(subject.name).to eq('knative') expect(subject.chart).to eq('knative/knative') expect(subject.files).to eq(knative.files) end - it 'should not install metrics for prometheus' do + it 'does not install metrics for prometheus' do expect(subject.postinstall).to be_nil end @@ -97,7 +97,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } - it 'should install metrics' do + it 'installs metrics' do expect(subject.postinstall).not_to be_nil expect(subject.postinstall.length).to be(1) expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") @@ -108,7 +108,7 @@ describe Clusters::Applications::Knative do describe '#install_command' do subject { knative.install_command } - it 'should be initialized with latest version' do + it 'is initialized with latest version' do expect(subject.version).to eq('0.3.0') end @@ -119,7 +119,7 @@ describe Clusters::Applications::Knative do let!(:current_installed_version) { knative.version = '0.1.0' } subject { knative.update_command } - it 'should be initialized with current version' do + it 'is initialized with current version' do expect(subject.version).to eq(current_installed_version) end @@ -132,7 +132,7 @@ describe Clusters::Applications::Knative do subject { application.files } - it 'should include knative specific keys in the values.yaml file' do + it 'includes knative specific keys in the values.yaml file' do expect(values).to include('domain') end end @@ -165,7 +165,7 @@ describe Clusters::Applications::Knative do synchronous_reactive_cache(knative) end - it 'should be able k8s core for pod details' do + it 'is able k8s core for pod details' do expect(knative.service_pod_details(namespace.namespace, cluster.cluster_project.project.name)).not_to be_nil end end @@ -190,7 +190,7 @@ describe Clusters::Applications::Knative do stub_kubeclient_service_pods end - it 'should have an unintialized cache' do + it 'has an unintialized cache' do is_expected.to be_nil end @@ -204,11 +204,11 @@ describe Clusters::Applications::Knative do synchronous_reactive_cache(knative) end - it 'should have cached services' do + it 'has cached services' do is_expected.not_to be_nil end - it 'should match our namespace' do + it 'matches our namespace' do expect(knative.services_for(ns: namespace)).not_to be_nil end end diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 82a502addd4..e8ba9737c23 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -94,7 +94,7 @@ describe Clusters::Applications::Prometheus do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with 3 arguments' do + it 'is initialized with 3 arguments' do expect(subject.name).to eq('prometheus') expect(subject.chart).to eq('stable/prometheus') expect(subject.version).to eq('6.7.3') @@ -113,12 +113,12 @@ describe Clusters::Applications::Prometheus do context 'application failed to install previously' do let(:prometheus) { create(:clusters_applications_prometheus, :errored, version: '2.0.0') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('6.7.3') end end - it 'should not install knative metrics' do + it 'does not install knative metrics' do expect(subject.postinstall).to be_nil end @@ -128,7 +128,7 @@ describe Clusters::Applications::Prometheus do subject { prometheus.install_command } - it 'should install knative metrics' do + it 'installs knative metrics' do expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") end end @@ -142,7 +142,7 @@ describe Clusters::Applications::Prometheus do expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::InstallCommand) end - it 'should be initialized with 3 arguments' do + it 'is initialized with 3 arguments' do command = prometheus.upgrade_command(values) expect(command.name).to eq('prometheus') @@ -180,7 +180,7 @@ describe Clusters::Applications::Prometheus do subject { application.files } - it 'should include prometheus valid values' do + it 'includes prometheus valid values' do expect(values).to include('alertmanager') expect(values).to include('kubeStateMetrics') expect(values).to include('nodeExporter') @@ -204,7 +204,7 @@ describe Clusters::Applications::Prometheus do expect(subject[:'values.yaml']).to eq({ hello: :world }) end - it 'should include cert files' do + it 'includes cert files' do expect(subject[:'ca.pem']).to be_present expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) @@ -220,7 +220,7 @@ describe Clusters::Applications::Prometheus do application.cluster.application_helm.ca_cert = nil end - it 'should not include cert files' do + it 'does not include cert files' do expect(subject[:'ca.pem']).not_to be_present expect(subject[:'cert.pem']).not_to be_present expect(subject[:'key.pem']).not_to be_present diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 7e2f5835279..399a13f82cb 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -21,7 +21,7 @@ describe Clusters::Applications::Runner do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } - it 'should be initialized with 4 arguments' do + it 'is initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') expect(subject.version).to eq('0.3.0') @@ -41,7 +41,7 @@ describe Clusters::Applications::Runner do context 'application failed to install previously' do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } - it 'should be initialized with the locked version' do + it 'is initialized with the locked version' do expect(subject.version).to eq('0.3.0') end end @@ -53,7 +53,7 @@ describe Clusters::Applications::Runner do subject { application.files } - it 'should include runner valid values' do + it 'includes runner valid values' do expect(values).to include('concurrent') expect(values).to include('checkInterval') expect(values).to include('rbac') @@ -131,7 +131,7 @@ describe Clusters::Applications::Runner do allow(application).to receive(:chart_values).and_return(stub_values) end - it 'should overwrite values.yaml' do + it 'overwrites values.yaml' do expect(values).to match(/privileged: '?#{application.privileged}/) end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index fabd2806d9a..894ef3fb956 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -269,7 +269,7 @@ describe Clusters::Cluster do context 'when cluster is not a valid hostname' do let(:cluster) { build(:cluster, domain: 'http://not.a.valid.hostname') } - it 'should add an error on domain' do + it 'adds an error on domain' do expect(subject).not_to be_valid expect(subject.errors[:domain].first).to eq('contains invalid characters (valid characters: [a-z0-9\\-])') end @@ -599,7 +599,7 @@ describe Clusters::Cluster do stub_application_setting(auto_devops_domain: 'global_domain.com') end - it 'should include KUBE_INGRESS_BASE_DOMAIN' do + it 'includes KUBE_INGRESS_BASE_DOMAIN' do expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com') end end @@ -607,7 +607,7 @@ describe Clusters::Cluster do context 'with a cluster domain' do let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') } - it 'should include KUBE_INGRESS_BASE_DOMAIN' do + it 'includes KUBE_INGRESS_BASE_DOMAIN' do expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com') end end @@ -615,7 +615,7 @@ describe Clusters::Cluster do context 'with no domain' do let(:cluster) { create(:cluster, :provided_by_gcp, :project) } - it 'should return an empty array' do + it 'returns an empty array' do expect(subject.to_hash).to be_empty end end diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index 579f486f99f..b5cba80b806 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do context 'when platform has a namespace assigned' do let(:namespace) { 'platform-namespace' } - it 'should copy the namespace' do + it 'copies the namespace' do subject expect(kubernetes_namespace.namespace).to eq('platform-namespace') @@ -72,7 +72,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do let(:namespace) { nil } let(:project_slug) { "#{project.path}-#{project.id}" } - it 'should fallback to project namespace' do + it 'fallbacks to project namespace' do subject expect(kubernetes_namespace.namespace).to eq(project_slug) @@ -83,7 +83,7 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do describe '#service_account_name' do let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" } - it 'should set a service account name based on namespace' do + it 'sets a service account name based on namespace' do subject expect(kubernetes_namespace.service_account_name).to eq(service_account_name) diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 14bec17a2bd..0281dd2c303 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -447,7 +447,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching let(:platform) { cluster.platform } context 'when namespace is updated' do - it 'should call ConfigureWorker' do + it 'calls ConfigureWorker' do expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once platform.namespace = 'new-namespace' @@ -456,7 +456,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end context 'when namespace is not updated' do - it 'should not call ConfigureWorker' do + it 'does not call ConfigureWorker' do expect(ClusterConfigureWorker).not_to receive(:perform_async) platform.username = "new-username" diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 259ac6852a8..27ed298ae08 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -659,7 +659,7 @@ describe Issuable do end context 'adding time' do - it 'should update the total time spent' do + it 'updates the total time spent' do spend_time(1800) expect(issue.total_time_spent).to eq(1800) @@ -679,7 +679,7 @@ describe Issuable do spend_time(1800) end - it 'should update the total time spent' do + it 'updates the total time spent' do spend_time(-900) expect(issue.total_time_spent).to eq(900) diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb index 650d49e41a1..67353475251 100644 --- a/spec/models/concerns/spammable_spec.rb +++ b/spec/models/concerns/spammable_spec.rb @@ -12,7 +12,7 @@ describe Spammable do end describe 'ClassMethods' do - it 'should return correct attr_spammable' do + it 'returns correct attr_spammable' do expect(issue.spammable_text).to eq("#{issue.title}\n#{issue.description}") end end @@ -20,7 +20,7 @@ describe Spammable do describe 'InstanceMethods' do let(:issue) { build(:issue, spam: true) } - it 'should be invalid if spam' do + it 'is invalid if spam' do expect(issue.valid?).to be_falsey end diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 05320703e25..2fe82eaa778 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -9,7 +9,7 @@ describe DeployToken do it { is_expected.to have_many(:projects).through(:project_deploy_tokens) } describe '#ensure_token' do - it 'should ensure a token' do + it 'ensures a token' do deploy_token.token = nil deploy_token.save @@ -19,13 +19,13 @@ describe DeployToken do describe '#ensure_at_least_one_scope' do context 'with at least one scope' do - it 'should be valid' do + it 'is valid' do is_expected.to be_valid end end context 'with no scopes' do - it 'should be invalid' do + it 'is invalid' do deploy_token = build(:deploy_token, read_repository: false, read_registry: false) expect(deploy_token).not_to be_valid @@ -36,13 +36,13 @@ describe DeployToken do describe '#scopes' do context 'with all the scopes' do - it 'should return scopes assigned to DeployToken' do + it 'returns scopes assigned to DeployToken' do expect(deploy_token.scopes).to eq([:read_repository, :read_registry]) end end context 'with only one scope' do - it 'should return scopes assigned to DeployToken' do + it 'returns scopes assigned to DeployToken' do deploy_token = create(:deploy_token, read_registry: false) expect(deploy_token.scopes).to eq([:read_repository]) end @@ -50,7 +50,7 @@ describe DeployToken do end describe '#revoke!' do - it 'should update revoke attribute' do + it 'updates revoke attribute' do deploy_token.revoke! expect(deploy_token.revoked?).to be_truthy end @@ -58,20 +58,20 @@ describe DeployToken do describe "#active?" do context "when it has been revoked" do - it 'should return false' do + it 'returns false' do deploy_token.revoke! expect(deploy_token.active?).to be_falsy end end context "when it hasn't been revoked and is not expired" do - it 'should return true' do + it 'returns true' do expect(deploy_token.active?).to be_truthy end end context "when it hasn't been revoked and is expired" do - it 'should return true' do + it 'returns true' do deploy_token.update_attribute(:expires_at, Date.today - 5.days) expect(deploy_token.active?).to be_falsy end @@ -80,7 +80,7 @@ describe DeployToken do context "when it hasn't been revoked and has no expiry" do let(:deploy_token) { create(:deploy_token, expires_at: nil) } - it 'should return true' do + it 'returns true' do expect(deploy_token.active?).to be_truthy end end @@ -126,7 +126,7 @@ describe DeployToken do context 'when using Forever.date' do let(:deploy_token) { create(:deploy_token, expires_at: nil) } - it 'should return nil' do + it 'returns nil' do expect(deploy_token.expires_at).to be_nil end end @@ -135,7 +135,7 @@ describe DeployToken do let(:expires_at) { Date.today + 5.months } let(:deploy_token) { create(:deploy_token, expires_at: expires_at) } - it 'should return the personalized date' do + it 'returns the personalized date' do expect(deploy_token.expires_at).to eq(expires_at) end end @@ -145,7 +145,7 @@ describe DeployToken do context 'when passing nil' do let(:deploy_token) { create(:deploy_token, expires_at: nil) } - it 'should assign Forever.date' do + it 'assigns Forever.date' do expect(deploy_token.read_attribute(:expires_at)).to eq(Forever.date) end end @@ -154,7 +154,7 @@ describe DeployToken do let(:expires_at) { Date.today + 5.months } let(:deploy_token) { create(:deploy_token, expires_at: expires_at) } - it 'should respect the value' do + it 'respects the value' do expect(deploy_token.read_attribute(:expires_at)).to eq(expires_at) end end @@ -166,14 +166,14 @@ describe DeployToken do subject { project.deploy_tokens.gitlab_deploy_token } context 'with a gitlab deploy token associated' do - it 'should return the gitlab deploy token' do + it 'returns the gitlab deploy token' do deploy_token = create(:deploy_token, :gitlab_deploy_token, projects: [project]) is_expected.to eq(deploy_token) end end context 'with no gitlab deploy token associated' do - it 'should return nil' do + it 'returns nil' do is_expected.to be_nil end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index b2ffd5812ab..e6e7298a043 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -788,14 +788,14 @@ describe Group do describe '#has_parent?' do context 'when the group has a parent' do - it 'should be truthy' do + it 'is truthy' do group = create(:group, :nested) expect(group.has_parent?).to be_truthy end end context 'when the group has no parent' do - it 'should be falsy' do + it 'is falsy' do group = create(:group, parent: nil) expect(group.has_parent?).to be_falsy end @@ -959,4 +959,12 @@ describe Group do end end end + + describe 'project_creation_level' do + it 'outputs the default one if it is nil' do + group = create(:group, project_creation_level: nil) + + expect(group.project_creation_level).to eq(Gitlab::CurrentSettings.default_project_creation) + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 62e7dd3231b..387d1221c76 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -740,14 +740,14 @@ describe Namespace do describe '#full_path_was' do context 'when the group has no parent' do - it 'should return the path was' do + it 'returns the path was' do group = create(:group, parent: nil) expect(group.full_path_was).to eq(group.path_was) end end context 'when a parent is assigned to a group with no previous parent' do - it 'should return the path was' do + it 'returns the path was' do group = create(:group, parent: nil) parent = create(:group) @@ -758,7 +758,7 @@ describe Namespace do end context 'when a parent is removed from the group' do - it 'should return the parent full path' do + it 'returns the parent full path' do parent = create(:group) group = create(:group, parent: parent) group.parent = nil @@ -768,7 +768,7 @@ describe Namespace do end context 'when changing parents' do - it 'should return the previous parent full path' do + it 'returns the previous parent full path' do parent = create(:group) group = create(:group, parent: parent) new_parent = create(:group) diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index d1a2bedf542..232172fde76 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -22,7 +22,7 @@ describe Network::Graph do expect(commits).to all( be_kind_of(Network::Commit) ) end - it 'it the commits by commit date (descending)' do + it 'sorts commits by commit date (descending)' do # Remove duplicate timestamps because they make it harder to # assert that the commits are sorted as expected. commits = graph.commits.uniq(&:date) diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 8ad28ce68cc..b81e5610e2c 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -117,7 +117,7 @@ describe ProjectAutoDevops do context 'when the project is public' do let(:project) { create(:project, :repository, :public) } - it 'should not create a gitlab deploy token' do + it 'does not create a gitlab deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } @@ -127,7 +127,7 @@ describe ProjectAutoDevops do context 'when the project is internal' do let(:project) { create(:project, :repository, :internal) } - it 'should create a gitlab deploy token' do + it 'creates a gitlab deploy token' do expect do auto_devops.save end.to change { DeployToken.count }.by(1) @@ -137,7 +137,7 @@ describe ProjectAutoDevops do context 'when the project is private' do let(:project) { create(:project, :repository, :private) } - it 'should create a gitlab deploy token' do + it 'creates a gitlab deploy token' do expect do auto_devops.save end.to change { DeployToken.count }.by(1) @@ -148,7 +148,7 @@ describe ProjectAutoDevops do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'should create a deploy token' do + it 'creates a deploy token' do expect do auto_devops.save end.to change { DeployToken.count }.by(1) @@ -159,7 +159,7 @@ describe ProjectAutoDevops do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, enabled: nil, project: project) } - it 'should create a deploy token' do + it 'creates a deploy token' do allow(Gitlab::CurrentSettings).to receive(:auto_devops_enabled?).and_return(true) expect do @@ -172,7 +172,7 @@ describe ProjectAutoDevops do let(:project) { create(:project, :repository, :internal) } let(:auto_devops) { build(:project_auto_devops, :disabled, project: project) } - it 'should not create a deploy token' do + it 'does not create a deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } @@ -184,7 +184,7 @@ describe ProjectAutoDevops do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'should not create a deploy token' do + it 'does not create a deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } @@ -196,7 +196,7 @@ describe ProjectAutoDevops do let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) } let(:auto_devops) { build(:project_auto_devops, project: project) } - it 'should not create a deploy token' do + it 'does not create a deploy token' do expect do auto_devops.save end.not_to change { DeployToken.count } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 7bf093b71e7..3a381cb405d 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -70,11 +70,11 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do kubernetes_service.properties['namespace'] = "foo" end - it 'should not update attributes' do + it 'does not update attributes' do expect(kubernetes_service.save).to be_falsy end - it 'should include an error with a deprecation message' do + it 'includes an error with a deprecation message' do kubernetes_service.valid? expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been deprecated/) end @@ -83,7 +83,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'with a non-deprecated service' do let(:kubernetes_service) { create(:kubernetes_service) } - it 'should update attributes' do + it 'updates attributes' do kubernetes_service.properties['namespace'] = 'foo' expect(kubernetes_service.save).to be_truthy end @@ -98,15 +98,15 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do kubernetes_service.save end - it 'should deactive the service' do + it 'deactivates the service' do expect(kubernetes_service.active?).to be_falsy end - it 'should not include a deprecation message as error' do + it 'does not include a deprecation message as error' do expect(kubernetes_service.errors.messages.count).to eq(0) end - it 'should update attributes' do + it 'updates attributes' do expect(kubernetes_service.properties['namespace']).to eq("foo") end end @@ -118,7 +118,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do kubernetes_service.properties['namespace'] = 'foo' end - it 'should update attributes' do + it 'updates attributes' do expect(kubernetes_service.save).to be_truthy expect(kubernetes_service.properties['namespace']).to eq('foo') end @@ -392,13 +392,13 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do let(:kubernetes_service) { create(:kubernetes_service) } context 'with an active kubernetes service' do - it 'should return false' do + it 'returns false' do expect(kubernetes_service.deprecated?).to be_falsy end end context 'with a inactive kubernetes service' do - it 'should return true' do + it 'returns true' do kubernetes_service.update_attribute(:active, false) expect(kubernetes_service.deprecated?).to be_truthy end @@ -408,18 +408,18 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do describe "#deprecation_message" do let(:kubernetes_service) { create(:kubernetes_service) } - it 'should indicate the service is deprecated' do + it 'indicates the service is deprecated' do expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been deprecated/) end context 'if the services is active' do - it 'should return a message' do + it 'returns a message' do expect(kubernetes_service.deprecation_message).to match(/Your Kubernetes cluster information on this page is still editable/) end end context 'if the service is not active' do - it 'should return a message' do + it 'returns a message' do kubernetes_service.update_attribute(:active, false) expect(kubernetes_service.deprecation_message).to match(/Fields on this page are now uneditable/) end diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index de2c8790405..773b8b7890f 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -56,7 +56,7 @@ describe PivotaltrackerService do WebMock.stub_request(:post, url) end - it 'should post correct message' do + it 'posts correct message' do service.execute(push_data) expect(WebMock).to have_requested(:post, url).with( body: { @@ -81,14 +81,14 @@ describe PivotaltrackerService do end end - it 'should post message if branch is in the list' do + it 'posts message if branch is in the list' do service.execute(push_data(branch: 'master')) service.execute(push_data(branch: 'v10')) expect(WebMock).to have_requested(:post, url).twice end - it 'should not post message if branch is not in the list' do + it 'does not post message if branch is not in the list' do service.execute(push_data(branch: 'mas')) service.execute(push_data(branch: 'v11')) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 33e514cd7b9..5eb31430ccd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -415,7 +415,7 @@ describe Project do project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end - it 'should return .external pipelines' do + it 'returns .external pipelines' do expect(project.all_pipelines).to all(have_attributes(source: 'external')) expect(project.all_pipelines.size).to eq(1) end @@ -439,7 +439,7 @@ describe Project do project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) end - it 'should return .external pipelines' do + it 'returns .external pipelines' do expect(project.ci_pipelines).to all(have_attributes(source: 'external')) expect(project.ci_pipelines.size).to eq(1) end @@ -1910,7 +1910,7 @@ describe Project do tags: %w[latest rc1]) end - it 'should have image tags' do + it 'has image tags' do expect(project).to have_container_registry_tags end end @@ -1921,7 +1921,7 @@ describe Project do tags: %w[latest rc1 pre1]) end - it 'should have image tags' do + it 'has image tags' do expect(project).to have_container_registry_tags end end @@ -1931,7 +1931,7 @@ describe Project do stub_container_registry_tags(repository: :any, tags: []) end - it 'should not have image tags' do + it 'does not have image tags' do expect(project).not_to have_container_registry_tags end end @@ -1942,16 +1942,16 @@ describe Project do stub_container_registry_config(enabled: false) end - it 'should not have image tags' do + it 'does not have image tags' do expect(project).not_to have_container_registry_tags end - it 'should not check root repository tags' do + it 'does not check root repository tags' do expect(project).not_to receive(:full_path) expect(project).not_to have_container_registry_tags end - it 'should iterate through container repositories' do + it 'iterates through container repositories' do expect(project).to receive(:container_repositories) expect(project).not_to have_container_registry_tags end @@ -2638,7 +2638,7 @@ describe Project do let!(:cluster) { kubernetes_namespace.cluster } let(:project) { kubernetes_namespace.project } - it 'should return token from kubernetes namespace' do + 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 } ) diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 0478094034a..f743dfed31f 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -7,14 +7,14 @@ describe RemoteMirror, :mailer do describe 'URL validation' do context 'with a valid URL' do - it 'should be valid' do + it 'is valid' do remote_mirror = build(:remote_mirror) expect(remote_mirror).to be_valid end end context 'with an invalid URL' do - it 'should not be valid' do + it 'is not valid' do remote_mirror = build(:remote_mirror, url: 'ftp://invalid.invalid') expect(remote_mirror).not_to be_valid diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 2578208659a..3f5d285bc2c 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -50,7 +50,7 @@ describe Repository do it { is_expected.not_to include('fix') } describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.branch_names_contains(sample_commit.id) end @@ -225,7 +225,7 @@ describe Repository do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.last_commit_id_for_path(sample_commit.id, '.gitignore') end @@ -249,7 +249,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.last_commit_for_path(sample_commit.id, '.gitignore').id end @@ -390,7 +390,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error { broken_repository.find_commits_by_message('s') } end end @@ -726,7 +726,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.search_files_by_content('feature', 'master') end @@ -775,7 +775,7 @@ describe Repository do end describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error { broken_repository.search_files_by_name('files', 'master') } end end @@ -817,7 +817,7 @@ describe Repository do let(:broken_repository) { create(:project, :broken_storage).repository } describe 'when storage is broken', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error do broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2') end @@ -1018,7 +1018,7 @@ describe Repository do repository.add_branch(project.creator, ref, 'master') end - it 'should be true' do + it 'is true' do is_expected.to eq(true) end end @@ -1028,7 +1028,7 @@ describe Repository do repository.add_tag(project.creator, ref, 'master') end - it 'should be false' do + it 'is false' do is_expected.to eq(false) end end @@ -1152,7 +1152,7 @@ describe Repository do end context 'with broken storage', :broken_storage do - it 'should raise a storage error' do + it 'raises a storage error' do expect_to_raise_storage_error { broken_repository.exists? } end end @@ -2249,11 +2249,11 @@ describe Repository do let(:commit) { repository.commit } let(:ancestor) { commit.parents.first } - it 'it is an ancestor' do + it 'is an ancestor' do expect(repository.ancestor?(ancestor.id, commit.id)).to eq(true) end - it 'it is not an ancestor' do + it 'is not an ancestor' do expect(repository.ancestor?(commit.id, ancestor.id)).to eq(false) end diff --git a/spec/models/serverless/function_spec.rb b/spec/models/serverless/function_spec.rb new file mode 100644 index 00000000000..1854d5f9415 --- /dev/null +++ b/spec/models/serverless/function_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Serverless::Function do + let(:project) { create(:project) } + let(:func) { described_class.new(project, 'test', 'test-ns') } + + it 'has a proper id' do + expect(func.id).to eql("#{project.id}/test/test-ns") + expect(func.name).to eql("test") + expect(func.namespace).to eql("test-ns") + end + + it 'can decode an identifier' do + f = described_class.find_by_id("#{project.id}/testfunc/dummy-ns") + + expect(f.name).to eql("testfunc") + expect(f.namespace).to eql("dummy-ns") + end +end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 2f025038bab..64db32781fe 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -291,7 +291,7 @@ describe Service do describe "#deprecated?" do let(:project) { create(:project, :repository) } - it 'should return false by default' do + it 'returns false by default' do service = create(:service, project: project) expect(service.deprecated?).to be_falsy end @@ -300,7 +300,7 @@ describe Service do describe "#deprecation_message" do let(:project) { create(:project, :repository) } - it 'should be empty by default' do + it 'is empty by default' do service = create(:service, project: project) expect(service.deprecation_message).to be_nil end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b7e36748fa2..a45a2737b13 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -98,6 +98,11 @@ describe User do end describe 'validations' do + describe 'name' do + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_length_of(:name).is_at_most(128) } + end + describe 'username' do it 'validates presence' do expect(subject).to validate_presence_of(:username) diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index dc98baca6dc..59f3a961d50 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -347,6 +347,120 @@ describe GroupPolicy do end end + context "create_projects" do + context 'when group has no project creation level set' do + let(:group) { create(:group, project_creation_level: nil) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + + context 'when group has project creation level set to no one' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_disallowed(:create_projects) } + end + end + + context 'when group has project creation level set to maintainer only' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + + context 'when group has project creation level set to developers + maintainer' do + let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + + context 'reporter' do + let(:current_user) { reporter } + + it { is_expected.to be_disallowed(:create_projects) } + end + + context 'developer' do + let(:current_user) { developer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'maintainer' do + let(:current_user) { maintainer } + + it { is_expected.to be_allowed(:create_projects) } + end + + context 'owner' do + let(:current_user) { owner } + + it { is_expected.to be_allowed(:create_projects) } + end + end + end + it_behaves_like 'clusterable policies' do let(:clusterable) { create(:group) } let(:cluster) do diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index 676835b3880..e202f7a9b5f 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -180,7 +180,7 @@ describe Ci::BuildPresenter do context 'When build has failed and retried' do let(:build) { create(:ci_build, :script_failure, :retried, pipeline: pipeline) } - it 'should include the reason of failure and the retried title' do + it 'includes the reason of failure and the retried title' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - failed - (script failure) (retried)") @@ -190,7 +190,7 @@ describe Ci::BuildPresenter do context 'When build has failed and is allowed to' do let(:build) { create(:ci_build, :script_failure, :allowed_to_fail, pipeline: pipeline) } - it 'should include the reason of failure' do + it 'includes the reason of failure' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - failed - (script failure) (allowed to fail)") @@ -200,7 +200,7 @@ describe Ci::BuildPresenter do context 'For any other build (no retried)' do let(:build) { create(:ci_build, :success, pipeline: pipeline) } - it 'should include build name and status' do + it 'includes build name and status' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - passed") @@ -210,7 +210,7 @@ describe Ci::BuildPresenter do context 'For any other build (retried)' do let(:build) { create(:ci_build, :success, :retried, pipeline: pipeline) } - it 'should include build name and status' do + it 'includes build name and status' do tooltip = subject.tooltip_message expect(tooltip).to eq("#{build.name} - passed (retried)") @@ -269,7 +269,7 @@ describe Ci::BuildPresenter do context 'when is a script or missing dependency failure' do let(:failure_reasons) { %w(script_failure missing_dependency_failure archived_failure) } - it 'should return false' do + it 'returns false' do failure_reasons.each do |failure_reason| build.update_attribute(:failure_reason, failure_reason) expect(presenter.recoverable?).to be_falsy @@ -280,7 +280,7 @@ describe Ci::BuildPresenter do context 'when is any other failure type' do let(:failure_reasons) { %w(unknown_failure api_failure stuck_or_timeout_failure runner_system_failure) } - it 'should return true' do + it 'returns true' do failure_reasons.each do |failure_reason| build.update_attribute(:failure_reason, failure_reason) expect(presenter.recoverable?).to be_truthy diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 537194b8e11..0919540e4ba 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -498,6 +498,40 @@ describe API::Internal do end end + context "console message" do + before do + project.add_developer(user) + end + + context "git pull" do + context "with no console message" do + it "has the correct payload" do + pull(key, project) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['gl_console_messages']).to eq([]) + end + end + + context "with a console message" do + let(:console_messages) { ['message for the console'] } + + it "has the correct payload" do + expect_next_instance_of(Gitlab::GitAccess) do |access| + expect(access).to receive(:check_for_console_messages) + .with('git-upload-pack') + .and_return(console_messages) + end + + pull(key, project) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['gl_console_messages']).to eq(console_messages) + end + end + end + end + context "blocked user" do let(:personal_project) { create(:project, namespace: user.namespace) } @@ -610,6 +644,22 @@ describe API::Internal do expect(response).to have_gitlab_http_status(404) expect(json_response["status"]).to be_falsey end + + it 'returns a 200 response when using a project path that does not exist' do + post( + api("/internal/allowed"), + params: { + key_id: key.id, + project: 'project/does-not-exist.git', + action: 'git-upload-pack', + secret_token: secret_token, + protocol: 'ssh' + } + ) + + expect(response).to have_gitlab_http_status(404) + expect(json_response["status"]).to be_falsey + end end context 'user does not exist' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index a5434d3ea80..86484ce62f8 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -2189,6 +2189,18 @@ describe API::Issues do expect_paginated_array_response(related_mr.id) end + context 'merge request closes an issue' do + let!(:closing_issue_mr_rel) do + create(:merge_requests_closing_issues, issue: issue, merge_request: related_mr) + end + + it 'returns closing MR only once' do + get_related_merge_requests(project.id, issue.iid, user) + + expect_paginated_array_response([related_mr.id]) + end + end + context 'no merge request mentioned a issue' do it 'returns empty array' do get_related_merge_requests(project.id, closed_issue.iid, user) diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index c26d31c5e0d..9fed07cae82 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -435,7 +435,7 @@ describe API::Pipelines do end context 'unauthorized user' do - it 'should not return a project pipeline' do + it 'does not return a project pipeline' do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(404) @@ -481,7 +481,7 @@ describe API::Pipelines do context 'unauthorized user' do context 'when user is not member' do - it 'should return a 404' do + it 'returns a 404' do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) expect(response).to have_gitlab_http_status(404) @@ -496,7 +496,7 @@ describe API::Pipelines do project.add_developer(developer) end - it 'should return a 403' do + it 'returns a 403' do delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", developer) expect(response).to have_gitlab_http_status(403) @@ -526,7 +526,7 @@ describe API::Pipelines do end context 'unauthorized user' do - it 'should not return a project pipeline' do + it 'does not return a project pipeline' do post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) expect(response).to have_gitlab_http_status(404) diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 81442125a1c..94e6ca2c07c 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -22,7 +22,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do get api("/projects/#{project.id}/clusters", non_member) expect(response).to have_gitlab_http_status(404) @@ -34,15 +34,15 @@ describe API::ProjectClusters do get api("/projects/#{project.id}/clusters", current_user) end - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end - it 'should include pagination headers' do + it 'includes pagination headers' do expect(response).to include_pagination_headers end - it 'should only include authorized clusters' do + it 'onlies include authorized clusters' do cluster_ids = json_response.map { |cluster| cluster['id'] } expect(cluster_ids).to match_array(clusters.pluck(:id)) @@ -67,7 +67,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member) expect(response).to have_gitlab_http_status(404) @@ -132,7 +132,7 @@ describe API::ProjectClusters do projects: [project]) end - it 'should not include GCP provider info' do + it 'does not include GCP provider info' do expect(json_response['provider_gcp']).not_to be_present end end @@ -194,7 +194,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params expect(response).to have_gitlab_http_status(404) @@ -207,11 +207,11 @@ describe API::ProjectClusters do end context 'with valid params' do - it 'should respond with 201' do + it 'responds with 201' do expect(response).to have_gitlab_http_status(201) end - it 'should create a new Cluster::Cluster' do + it 'creates a new Cluster::Cluster' do cluster_result = Clusters::Cluster.find(json_response["id"]) platform_kubernetes = cluster_result.platform @@ -246,7 +246,7 @@ describe API::ProjectClusters do context 'when user sets authorization type as ABAC' do let(:authorization_type) { 'abac' } - it 'should create an ABAC cluster' do + it 'creates an ABAC cluster' do cluster_result = Clusters::Cluster.find(json_response['id']) expect(cluster_result.platform.abac?).to be_truthy @@ -256,15 +256,15 @@ describe API::ProjectClusters do context 'with invalid params' do let(:namespace) { 'invalid_namespace' } - it 'should respond with 400' do + it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end - it 'should not create a new Clusters::Cluster' do + it 'does not create a new Clusters::Cluster' do expect(project.reload.clusters).to be_empty end - it 'should return validation errors' do + it 'returns validation errors' do expect(json_response['message']['platform_kubernetes.namespace'].first).to be_present end end @@ -278,11 +278,11 @@ describe API::ProjectClusters do post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params end - it 'should respond with 403' do + it 'responds with 403' do expect(response).to have_gitlab_http_status(403) end - it 'should return an appropriate message' do + it 'returns an appropriate message' do expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters') end end @@ -314,7 +314,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params expect(response).to have_gitlab_http_status(404) @@ -329,11 +329,11 @@ describe API::ProjectClusters do end context 'with valid params' do - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end - it 'should update cluster attributes' do + it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') end @@ -342,17 +342,17 @@ describe API::ProjectClusters do context 'with invalid params' do let(:namespace) { 'invalid_namespace' } - it 'should respond with 400' do + it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end - it 'should not update cluster attributes' 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.namespace).not_to eq('invalid_namespace') end - it 'should return validation errors' do + it 'returns validation errors' do expect(json_response['message']['platform_kubernetes.namespace'].first).to match('can contain only lowercase letters') end end @@ -366,11 +366,11 @@ describe API::ProjectClusters do } end - it 'should respond with 400' do + it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end - it 'should return validation error' do + it 'returns validation error' do expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster') end end @@ -378,7 +378,7 @@ describe API::ProjectClusters do context 'when user tries to change namespace' do let(:namespace) { 'new-namespace' } - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end end @@ -407,11 +407,11 @@ describe API::ProjectClusters do } end - it 'should respond with 200' do + it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end - it 'should update platform kubernetes attributes' do + it 'updates platform kubernetes attributes' do platform_kubernetes = cluster.platform_kubernetes expect(cluster.name).to eq('new-name') @@ -424,7 +424,7 @@ describe API::ProjectClusters do context 'with a cluster that does not belong to user' do let(:cluster) { create(:cluster, :project, :provided_by_user) } - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end @@ -440,7 +440,7 @@ describe API::ProjectClusters do end context 'non-authorized user' do - it 'should respond with 404' do + it 'responds with 404' do delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params expect(response).to have_gitlab_http_status(404) @@ -452,18 +452,18 @@ describe API::ProjectClusters do delete api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: cluster_params end - it 'should respond with 204' do + it 'responds with 204' do expect(response).to have_gitlab_http_status(204) end - it 'should delete the cluster' do + it 'deletes the cluster' do expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy end context 'with a cluster that does not belong to user' do let(:cluster) { create(:cluster, :project, :provided_by_user) } - it 'should respond with 404' do + it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index f33eb5b9e02..f869325e892 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -44,6 +44,7 @@ describe API::Settings, 'Settings' do put api("/application/settings", admin), params: { default_projects_limit: 3, + default_project_creation: 2, password_authentication_enabled_for_web: false, repository_storages: ['custom'], plantuml_enabled: true, @@ -64,12 +65,13 @@ describe API::Settings, 'Settings' do performance_bar_allowed_group_path: group.full_path, instance_statistics_visibility_private: true, diff_max_patch_bytes: 150_000, - default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE, + default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE, local_markdown_version: 3 } expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) + expect(json_response['default_project_creation']).to eq(::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) expect(json_response['password_authentication_enabled_for_web']).to be_falsey expect(json_response['repository_storages']).to eq(['custom']) expect(json_response['plantuml_enabled']).to be_truthy diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb index be6aa7c65c3..dbfb3eace83 100644 --- a/spec/serializers/analytics_stage_serializer_spec.rb +++ b/spec/serializers/analytics_stage_serializer_spec.rb @@ -14,7 +14,7 @@ describe AnalyticsStageSerializer do allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({}) end - it 'it generates payload for single object' do + it 'generates payload for single object' do expect(subject).to be_kind_of Hash end diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb index 236c244b402..8fa0574bfd6 100644 --- a/spec/serializers/analytics_summary_serializer_spec.rb +++ b/spec/serializers/analytics_summary_serializer_spec.rb @@ -18,7 +18,7 @@ describe AnalyticsSummarySerializer do .to receive(:value).and_return(1.12) end - it 'it generates payload for single object' do + it 'generates payload for single object' do expect(subject).to be_kind_of Hash end diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index f6bd6e9ede4..1edf69dc290 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -112,5 +112,15 @@ describe BuildDetailsEntity do expect(subject['merge_request_path']).to be_nil end end + + context 'when the build has failed' do + let(:build) { create(:ci_build, :created) } + + before do + build.drop!(:unmet_prerequisites) + end + + it { is_expected.to include(failure_reason: 'unmet_prerequisites') } + end end end diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb index 791b64dc356..c2312734042 100644 --- a/spec/serializers/environment_entity_spec.rb +++ b/spec/serializers/environment_entity_spec.rb @@ -54,7 +54,7 @@ describe EnvironmentEntity do projects: [project]) end - it 'should include cluster_type' do + it 'includes cluster_type' do expect(subject).to include(:cluster_type) expect(subject[:cluster_type]).to eq('project_type') end @@ -65,7 +65,7 @@ describe EnvironmentEntity do create(:kubernetes_service, project: project) end - it 'should not include cluster_type' do + it 'does not include cluster_type' do expect(subject).not_to include(:cluster_type) end end diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb index 851b41a7f7e..8de61d4d466 100644 --- a/spec/serializers/job_entity_spec.rb +++ b/spec/serializers/job_entity_spec.rb @@ -154,15 +154,15 @@ describe JobEntity do expect(subject[:status][:label]).to eq('failed') end - it 'should indicate the failure reason on tooltip' do + it 'indicates the failure reason on tooltip' do expect(subject[:status][:tooltip]).to eq('failed - (API failure)') end - it 'should include a callout message with a verbose output' do + it 'includes a callout message with a verbose output' do expect(subject[:callout_message]).to eq('There has been an API failure, please try again') end - it 'should state that it is not recoverable' do + it 'states that it is not recoverable' do expect(subject[:recoverable]).to be_truthy end end @@ -178,15 +178,15 @@ describe JobEntity do expect(subject[:status][:label]).to eq('failed (allowed to fail)') end - it 'should indicate the failure reason on tooltip' do + it 'indicates the failure reason on tooltip' do expect(subject[:status][:tooltip]).to eq('failed - (API failure) (allowed to fail)') end - it 'should include a callout message with a verbose output' do + it 'includes a callout message with a verbose output' do expect(subject[:callout_message]).to eq('There has been an API failure, please try again') end - it 'should state that it is not recoverable' do + it 'states that it is not recoverable' do expect(subject[:recoverable]).to be_truthy end end @@ -194,7 +194,7 @@ describe JobEntity do context 'when the job failed with a script failure' do let(:job) { create(:ci_build, :failed, :script_failure) } - it 'should not include callout message or recoverable keys' do + it 'does not include callout message or recoverable keys' do expect(subject).not_to include('callout_message') expect(subject).not_to include('recoverable') end @@ -203,7 +203,7 @@ describe JobEntity do context 'when job failed and is recoverable' do let(:job) { create(:ci_build, :api_failure) } - it 'should state it is recoverable' do + it 'states it is recoverable' do expect(subject[:recoverable]).to be_truthy end end @@ -211,7 +211,7 @@ describe JobEntity do context 'when job passed' do let(:job) { create(:ci_build, :success) } - it 'should not include callout message or recoverable keys' do + it 'does not include callout message or recoverable keys' do expect(subject).not_to include('callout_message') expect(subject).not_to include('recoverable') end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 0e99ef38d2f..b89898f26f7 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -287,7 +287,7 @@ describe MergeRequestWidgetEntity do resource.commits.find { |c| c.short_id == short_id } end - it 'should not include merge commits' do + it 'does not include merge commits' do commits_in_widget = subject[:commits_without_merge_commits] expect(commits_in_widget.length).to be < resource.commits.length diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb index 11a65d0c300..382b9043566 100644 --- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb @@ -89,7 +89,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do it_behaves_like 'creates service account and token' - it 'should create a cluster role binding with cluster-admin access' do + it 'creates a cluster role binding with cluster-admin access' do subject expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with( diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb index 3a2bbf1ecd1..1bd0356a73b 100644 --- a/spec/services/deploy_tokens/create_service_spec.rb +++ b/spec/services/deploy_tokens/create_service_spec.rb @@ -9,11 +9,11 @@ describe DeployTokens::CreateService do subject { described_class.new(project, user, deploy_token_params).execute } context 'when the deploy token is valid' do - it 'should create a new DeployToken' do + it 'creates a new DeployToken' do expect { subject }.to change { DeployToken.count }.by(1) end - it 'should create a new ProjectDeployToken' do + it 'creates a new ProjectDeployToken' do expect { subject }.to change { ProjectDeployToken.count }.by(1) end @@ -25,7 +25,7 @@ describe DeployTokens::CreateService do context 'when expires at date is not passed' do let(:deploy_token_params) { attributes_for(:deploy_token, expires_at: '') } - it 'should set Forever.date' do + it 'sets Forever.date' do expect(subject.read_attribute(:expires_at)).to eq(Forever.date) end end @@ -33,11 +33,11 @@ describe DeployTokens::CreateService do context 'when the deploy token is invalid' do let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) } - it 'should not create a new DeployToken' do + it 'does not create a new DeployToken' do expect { subject }.not_to change { DeployToken.count } end - it 'should not create a new ProjectDeployToken' do + it 'does not create a new ProjectDeployToken' do expect { subject }.not_to change { ProjectDeployToken.count } end end diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index a92d3376f7b..730fccc599e 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -51,14 +51,28 @@ describe ErrorTracking::ListProjectsService do end context 'sentry client raises exception' do - before do - expect(error_tracking_setting).to receive(:list_sentry_projects) - .and_raise(Sentry::Client::Error, 'Sentry response status code: 500') + context 'Sentry::Client::Error' do + before do + expect(error_tracking_setting).to receive(:list_sentry_projects) + .and_raise(Sentry::Client::Error, 'Sentry response status code: 500') + end + + it 'returns error response' do + expect(result[:message]).to eq('Sentry response status code: 500') + expect(result[:http_status]).to eq(:bad_request) + end end - it 'returns error response' do - expect(result[:message]).to eq('Sentry response status code: 500') - expect(result[:http_status]).to eq(:bad_request) + context 'Sentry::Client::MissingKeysError' do + before do + expect(error_tracking_setting).to receive(:list_sentry_projects) + .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + + it 'returns error response' do + expect(result[:message]).to eq('Sentry API response is missing keys. key not found: "id"') + expect(result[:http_status]).to eq(:internal_server_error) + end end end diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index 79d504b9b45..0bc67dbb4a1 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -12,11 +12,11 @@ describe Groups::TransferService, :postgresql do allow(Group).to receive(:supports_nested_objects?).and_return(false) end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: Database is not supported.') end @@ -30,11 +30,11 @@ describe Groups::TransferService, :postgresql do create(:group_member, :owner, group: new_parent_group, user: user) end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: namespace directory cannot be moved') end @@ -50,7 +50,7 @@ describe Groups::TransferService, :postgresql do context 'when the group is already a root group' do let(:group) { create(:group, :public) } - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(nil) expect(transfer_service.error).to eq('Transfer failed: Group is already a root group.') end @@ -59,11 +59,11 @@ describe Groups::TransferService, :postgresql do context 'when the user does not have the right policies' do let!(:group_member) { create(:group_member, :guest, group: group, user: user) } - it "should return false" do + it "returns false" do expect(transfer_service.execute(nil)).to be_falsy end - it "should add an error on group" do + it "adds an error on group" do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq("Transfer failed: You don't have enough permissions.") end @@ -76,11 +76,11 @@ describe Groups::TransferService, :postgresql do create(:group, path: 'not-unique') end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(nil)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(nil) expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup with the same path.') end @@ -96,17 +96,17 @@ describe Groups::TransferService, :postgresql do group.reload end - it 'should update group attributes' do + it 'updates group attributes' do expect(group.parent).to be_nil end - it 'should update group children path' do + it 'updates group children path' do group.children.each do |subgroup| expect(subgroup.full_path).to eq("#{group.path}/#{subgroup.path}") end end - it 'should update group projects path' do + it 'updates group projects path' do group.projects.each do |project| expect(project.full_path).to eq("#{group.path}/#{project.path}") end @@ -122,11 +122,11 @@ describe Groups::TransferService, :postgresql do context 'when the new parent group is the same as the previous parent group' do let(:group) { create(:group, :public, :nested, parent: new_parent_group) } - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: Group is already associated to the parent group.') end @@ -135,11 +135,11 @@ describe Groups::TransferService, :postgresql do context 'when the user does not have the right policies' do let!(:group_member) { create(:group_member, :guest, group: group, user: user) } - it "should return false" do + it "returns false" do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it "should add an error on group" do + it "adds an error on group" do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq("Transfer failed: You don't have enough permissions.") end @@ -152,11 +152,11 @@ describe Groups::TransferService, :postgresql do create(:group, path: "not-unique", parent: new_parent_group) end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup with the same path.') end @@ -171,11 +171,11 @@ describe Groups::TransferService, :postgresql do group.update_attribute(:path, 'foo') end - it 'should return false' do + it 'returns false' do expect(transfer_service.execute(new_parent_group)).to be_falsy end - it 'should add an error on group' do + it 'adds an error on group' do transfer_service.execute(new_parent_group) expect(transfer_service.error).to eq('Transfer failed: Validation failed: Group URL has already been taken') end @@ -191,7 +191,7 @@ describe Groups::TransferService, :postgresql do let(:new_parent_group) { create(:group, :public) } let(:group) { create(:group, :private, :nested) } - it 'should not update the visibility for the group' do + it 'does not update the visibility for the group' do group.reload expect(group.private?).to be_truthy expect(group.visibility_level).not_to eq(new_parent_group.visibility_level) @@ -202,27 +202,27 @@ describe Groups::TransferService, :postgresql do let(:new_parent_group) { create(:group, :private) } let(:group) { create(:group, :public, :nested) } - it 'should update visibility level based on the parent group' do + it 'updates visibility level based on the parent group' do group.reload expect(group.private?).to be_truthy expect(group.visibility_level).to eq(new_parent_group.visibility_level) end end - it 'should update visibility for the group based on the parent group' do + it 'updates visibility for the group based on the parent group' do expect(group.visibility_level).to eq(new_parent_group.visibility_level) end - it 'should update parent group to the new parent ' do + it 'updates parent group to the new parent' do expect(group.parent).to eq(new_parent_group) end - it 'should return the group as children of the new parent' do + it 'returns the group as children of the new parent' do expect(new_parent_group.children.count).to eq(1) expect(new_parent_group.children.first).to eq(group) end - it 'should create a redirect for the group' do + it 'creates a redirect for the group' do expect(group.redirect_routes.count).to eq(1) end end @@ -236,21 +236,21 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update subgroups path' do + it 'updates subgroups path' do new_parent_path = new_parent_group.path group.children.each do |subgroup| expect(subgroup.full_path).to eq("#{new_parent_path}/#{group.path}/#{subgroup.path}") end end - it 'should create redirects for the subgroups' do + it 'creates redirects for the subgroups' do expect(group.redirect_routes.count).to eq(1) expect(subgroup1.redirect_routes.count).to eq(1) expect(subgroup2.redirect_routes.count).to eq(1) end context 'when the new parent has a higher visibility than the children' do - it 'should not update the children visibility' do + it 'does not update the children visibility' do expect(subgroup1.private?).to be_truthy expect(subgroup2.internal?).to be_truthy end @@ -261,7 +261,7 @@ describe Groups::TransferService, :postgresql do let!(:subgroup2) { create(:group, :public, parent: group) } let(:new_parent_group) { create(:group, :private) } - it 'should update children visibility to match the new parent' do + it 'updates children visibility to match the new parent' do group.children.each do |subgroup| expect(subgroup.private?).to be_truthy end @@ -279,21 +279,21 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update projects path' do + it 'updates projects path' do new_parent_path = new_parent_group.path group.projects.each do |project| expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}") end end - it 'should create permanent redirects for the projects' do + it 'creates permanent redirects for the projects' do expect(group.redirect_routes.count).to eq(1) expect(project1.redirect_routes.count).to eq(1) expect(project2.redirect_routes.count).to eq(1) end context 'when the new parent has a higher visibility than the projects' do - it 'should not update projects visibility' do + it 'does not update projects visibility' do expect(project1.private?).to be_truthy expect(project2.internal?).to be_truthy end @@ -304,7 +304,7 @@ describe Groups::TransferService, :postgresql do let!(:project2) { create(:project, :repository, :public, namespace: group) } let(:new_parent_group) { create(:group, :private) } - it 'should update projects visibility to match the new parent' do + it 'updates projects visibility to match the new parent' do group.projects.each do |project| expect(project.private?).to be_truthy end @@ -324,21 +324,21 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update subgroups path' do + it 'updates subgroups path' do new_parent_path = new_parent_group.path group.children.each do |subgroup| expect(subgroup.full_path).to eq("#{new_parent_path}/#{group.path}/#{subgroup.path}") end end - it 'should update projects path' do + it 'updates projects path' do new_parent_path = new_parent_group.path group.projects.each do |project| expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}") end end - it 'should create redirect for the subgroups and projects' do + it 'creates redirect for the subgroups and projects' do expect(group.redirect_routes.count).to eq(1) expect(subgroup1.redirect_routes.count).to eq(1) expect(subgroup2.redirect_routes.count).to eq(1) @@ -360,7 +360,7 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should update subgroups path' do + it 'updates subgroups path' do new_base_path = "#{new_parent_group.path}/#{group.path}" group.children.each do |children| expect(children.full_path).to eq("#{new_base_path}/#{children.path}") @@ -372,7 +372,7 @@ describe Groups::TransferService, :postgresql do end end - it 'should update projects path' do + it 'updates projects path' do new_parent_path = "#{new_parent_group.path}/#{group.path}" subgroup1.projects.each do |project| project_full_path = "#{new_parent_path}/#{project.namespace.path}/#{project.name}" @@ -380,7 +380,7 @@ describe Groups::TransferService, :postgresql do end end - it 'should create redirect for the subgroups and projects' do + it 'creates redirect for the subgroups and projects' do expect(group.redirect_routes.count).to eq(1) expect(project1.redirect_routes.count).to eq(1) expect(subgroup1.redirect_routes.count).to eq(1) @@ -402,7 +402,7 @@ describe Groups::TransferService, :postgresql do transfer_service.execute(new_parent_group) end - it 'should restore group and projects visibility' do + it 'restores group and projects visibility' do subgroup1.reload project1.reload expect(subgroup1.public?).to be_truthy diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 86e58fe06b9..74f1e83b362 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -58,8 +58,10 @@ describe Issues::BuildService do "> That has a quote\n"\ ">>>\n" note_result = " > This is a string\n"\ + " > \n"\ " > > with a blockquote\n"\ - " > > > That has a quote\n" + " > > > That has a quote\n"\ + " > \n" discussion = create(:diff_note_on_merge_request, note: note_text).to_discussion expect(service.item_for_discussion(discussion)).to include(note_result) end diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index af0a214c00f..39a2ef579dd 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -77,6 +77,22 @@ describe MergeRequests::AddTodoWhenBuildFailsService do service.execute(commit_status) end end + + context 'when build belongs to a merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, + ref: merge_request.merge_ref_path, + merge_request: merge_request, + merge_requests_as_head_pipeline: [merge_request]) + end + + let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) } + + it 'notifies the todo service' do + expect(todo_service).to receive(:merge_request_build_failed).with(merge_request) + service.execute(commit_status) + end + end end describe '#close' do @@ -106,6 +122,22 @@ describe MergeRequests::AddTodoWhenBuildFailsService do service.close(commit_status) end end + + context 'when build belongs to a merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, + ref: merge_request.merge_ref_path, + merge_request: merge_request, + merge_requests_as_head_pipeline: [merge_request]) + end + + let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) } + + it 'notifies the todo service' do + expect(todo_service).to receive(:merge_request_build_retried).with(merge_request) + service.close(commit_status) + end + end end describe '#close_all' do diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 52bbd4e794d..8b7db1b2f1f 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -95,7 +95,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do sha: '1234abcdef', status: 'success') end - it 'it does not merge request' do + it 'does not merge request' do expect(MergeWorker).not_to receive(:perform_async) service.trigger(old_pipeline) end @@ -112,6 +112,21 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do service.trigger(unrelated_pipeline) end end + + context 'when pipeline is merge request pipeline' do + let(:pipeline) do + create(:ci_pipeline, :success, + source: :merge_request_event, + ref: mr_merge_if_green_enabled.merge_ref_path, + merge_request: mr_merge_if_green_enabled, + merge_requests_as_head_pipeline: [mr_merge_if_green_enabled]) + end + + it 'merges the associated merge request' do + expect(MergeWorker).to receive(:perform_async) + service.trigger(pipeline) + end + end end describe "#cancel" do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 5cf3577f01f..bd10523bc94 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -454,35 +454,35 @@ describe MergeRequests::RefreshService do end let(:force_push_commit) { @project.commit('feature').id } - it 'should reload a new diff for a push to the forked project' do + it 'reloads a new diff for a push to the forked project' do expect do service.new(@fork_project, @user).execute(@oldrev, first_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should reload a new diff for a force push to the source branch' do + it 'reloads a new diff for a force push to the source branch' do expect do service.new(@fork_project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should reload a new diff for a force push to the target branch' do + it 'reloads a new diff for a force push to the target branch' do expect do service.new(@project, @user).execute(@oldrev, force_push_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should reload a new diff for a push to the target project that contains a commit in the MR' do + it 'reloads a new diff for a push to the target project that contains a commit in the MR' do expect do service.new(@project, @user).execute(@oldrev, first_commit, 'refs/heads/master') reload_mrs end.to change { forked_master_mr.merge_request_diffs.count }.by(1) end - it 'should not increase the diff count for a new push to target branch' do + it 'does not increase the diff count for a new push to target branch' do new_commit = @project.repository.create_file(@user, 'new-file.txt', 'A new file', message: 'This is a test', branch_name: 'master') diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 7d2b6d5b8a7..9efdf96bc64 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -185,6 +185,7 @@ describe Notes::QuickActionsService do end before do + stub_licensed_features(multiple_issue_assignees: false) project.add_maintainer(maintainer) project.add_maintainer(assignee) end diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb index 76977d7a1a7..fb1ab3f9949 100644 --- a/spec/services/projects/auto_devops/disable_service_spec.rb +++ b/spec/services/projects/auto_devops/disable_service_spec.rb @@ -46,7 +46,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do create(:ci_pipeline, :failed, :auto_devops_source, project: project) end - it 'should disable Auto DevOps for project' do + it 'disables Auto DevOps for project' do subject expect(auto_devops.enabled).to eq(false) @@ -58,7 +58,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do create_list(:ci_pipeline, 2, :failed, :auto_devops_source, project: project) end - it 'should explicitly disable Auto DevOps for project' do + it 'explicitly disables Auto DevOps for project' do subject expect(auto_devops.reload.enabled).to eq(false) @@ -70,7 +70,7 @@ describe Projects::AutoDevops::DisableService, '#execute' do create(:ci_pipeline, :success, :auto_devops_source, project: project) end - it 'should not disable Auto DevOps for project' do + it 'does not disable Auto DevOps for project' do subject expect(auto_devops.reload.enabled).to be_nil @@ -85,14 +85,14 @@ describe Projects::AutoDevops::DisableService, '#execute' do create(:ci_pipeline, :failed, :auto_devops_source, project: project) end - it 'should disable Auto DevOps for project' do + it 'disables Auto DevOps for project' do subject auto_devops = project.reload.auto_devops expect(auto_devops.enabled).to eq(false) end - it 'should create a ProjectAutoDevops record' do + it 'creates a ProjectAutoDevops record' do expect { subject }.to change { ProjectAutoDevops.count }.from(0).to(1) end end diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 4b6d0c51363..8455b9bc3cf 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -41,12 +41,12 @@ describe Projects::ParticipantsService do group.add_owner(user) end - it 'should return an url for the avatar' do + it 'returns an url for the avatar' do expect(service.groups.size).to eq 1 expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") end - it 'should return an url for the avatar with relative url' do + it 'returns an url for the avatar with relative url' do stub_config_setting(relative_url_root: '/gitlab') stub_config_setting(url: Settings.send(:build_gitlab_url)) diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb new file mode 100644 index 00000000000..4bdb20de4c9 --- /dev/null +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Prometheus::ProxyService do + include ReactiveCachingHelpers + + set(:project) { create(:project) } + set(:environment) { create(:environment, project: project) } + + describe '#initialize' do + let(:params) { ActionController::Parameters.new(query: '1').permit! } + + it 'initializes attributes' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.proxyable).to eq(environment) + expect(result.method).to eq('GET') + expect(result.path).to eq('query') + expect(result.params).to eq('query' => '1') + end + + it 'converts ActionController::Parameters into hash' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.params).to be_an_instance_of(Hash) + end + + context 'with unknown params' do + let(:params) { ActionController::Parameters.new(query: '1', other_param: 'val').permit! } + + it 'filters unknown params' do + result = described_class.new(environment, 'GET', 'query', params) + + expect(result.params).to eq('query' => '1') + end + end + end + + describe '#execute' do + let(:prometheus_adapter) { instance_double(PrometheusService) } + let(:params) { ActionController::Parameters.new(query: '1').permit! } + + subject { described_class.new(environment, 'GET', 'query', params) } + + context 'when prometheus_adapter is nil' do + before do + allow(environment).to receive(:prometheus_adapter).and_return(nil) + end + + it 'returns error' do + expect(subject.execute).to eq( + status: :error, + message: 'No prometheus server found', + http_status: :service_unavailable + ) + end + end + + context 'when prometheus_adapter cannot query' do + before do + allow(environment).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(false) + end + + it 'returns error' do + expect(subject.execute).to eq( + status: :error, + message: 'No prometheus server found', + http_status: :service_unavailable + ) + end + end + + context 'cannot proxy' do + subject { described_class.new(environment, 'POST', 'garbage', params) } + + it 'returns error' do + expect(subject.execute).to eq( + message: 'Proxy support for this API is not available currently', + status: :error + ) + end + end + + context 'with caching', :use_clean_rails_memory_store_caching do + let(:return_value) { { 'http_status' => 200, 'body' => 'body' } } + + let(:opts) do + [environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' }] + end + + before do + allow(environment).to receive(:prometheus_adapter) + .and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:can_query?).and_return(true) + end + + context 'when value present in cache' do + before do + stub_reactive_cache(subject, return_value, opts) + end + + it 'returns cached value' do + result = subject.execute + + expect(result[:http_status]).to eq(return_value[:http_status]) + expect(result[:body]).to eq(return_value[:body]) + end + end + + context 'when value not present in cache' do + it 'returns nil' do + expect(ReactiveCachingWorker) + .to receive(:perform_async) + .with(subject.class, subject.id, *opts) + + result = subject.execute + + expect(result).to eq(nil) + end + end + end + + context 'call prometheus api' do + let(:prometheus_client) { instance_double(Gitlab::PrometheusClient) } + + before do + synchronous_reactive_cache(subject) + + 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) + .and_return(prometheus_client) + end + + context 'connection to prometheus server succeeds' do + let(:rest_client_response) { instance_double(RestClient::Response) } + let(:prometheus_http_status_code) { 400 } + + let(:response_body) do + '{"status":"error","errorType":"bad_data","error":"parse error at char 1: no expression found in input"}' + end + + before do + allow(prometheus_client).to receive(:proxy).and_return(rest_client_response) + + allow(rest_client_response).to receive(:code) + .and_return(prometheus_http_status_code) + allow(rest_client_response).to receive(:body).and_return(response_body) + end + + it 'returns the http status code and body from prometheus' do + expect(subject.execute).to eq( + http_status: prometheus_http_status_code, + body: response_body, + status: :success + ) + end + end + + context 'connection to prometheus server fails' do + context 'prometheus client raises Gitlab::PrometheusClient::Error' do + before do + allow(prometheus_client).to receive(:proxy) + .and_raise(Gitlab::PrometheusClient::Error, 'Network connection error') + end + + it 'returns error' do + expect(subject.execute).to eq( + status: :error, + message: 'Network connection error', + http_status: :service_unavailable + ) + end + end + end + end + end + + describe '.from_cache' do + it 'initializes an instance of ProxyService class' do + result = described_class.from_cache( + environment.class.name, environment.id, 'GET', 'query', { 'query' => '1' } + ) + + expect(result).to be_an_instance_of(described_class) + expect(result.proxyable).to eq(environment) + expect(result.method).to eq('GET') + expect(result.path).to eq('query') + expect(result.params).to eq('query' => '1') + end + end +end diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb index b1260cf740a..9adaee6481b 100644 --- a/spec/services/task_list_toggle_service_spec.rb +++ b/spec/services/task_list_toggle_service_spec.rb @@ -113,4 +113,25 @@ describe TaskListToggleService do expect(toggler.execute).to be_falsey end + + it 'properly handles a GitLab blockquote' do + markdown = + <<-EOT.strip_heredoc + >>> + gitlab blockquote + >>> + + * [ ] Task 1 + * [x] Task 2 + EOT + + markdown_html = Banzai::Pipeline::FullPipeline.call(markdown, project: nil)[:output].to_html + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: true, + line_source: '* [ ] Task 1', line_number: 5) + + expect(toggler.execute).to be_truthy + expect(toggler.updated_markdown.lines[4]).to eq "* [x] Task 1\n" + expect(toggler.updated_markdown_html).to include('disabled checked> Task 1') + end end diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 42a086d58d2..5b79c40f27b 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -224,7 +224,7 @@ shared_examples 'discussion comments' do |resource_name| find(toggle_selector).click end - it 'should have "Start discussion" selected' do + it 'has "Start discussion" selected' do find("#{menu_selector} li", match: :first) items = all("#{menu_selector} li") @@ -267,7 +267,7 @@ shared_examples 'discussion comments' do |resource_name| end end - it 'should have "Comment" selected when opening the menu' do + it 'has "Comment" selected when opening the menu' do find(toggle_selector).click find("#{menu_selector} li", match: :first) diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index 08d1d7a6059..87f825152cf 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -7,6 +7,10 @@ module PrometheusHelpers %{avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) * 100} end + def prometheus_istio_query(function_name, kube_namespace) + %{floor(sum(rate(istio_revision_request_count{destination_configuration=\"#{function_name}\", destination_namespace=\"#{kube_namespace}\"}[1m])*30))} + end + def prometheus_ping_url(prometheus_query) query = { query: prometheus_query }.to_query diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb index a8b00004fe7..6aa59960092 100644 --- a/spec/support/redis/redis_shared_examples.rb +++ b/spec/support/redis/redis_shared_examples.rb @@ -90,7 +90,7 @@ RSpec.shared_examples "redis_shared_examples" do subject { described_class._raw_config } let(:config_file_name) { '/var/empty/doesnotexist' } - it 'should be frozen' do + it 'is frozen' do expect(subject).to be_frozen end diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb index 3ad6e067674..ee5cfcd850d 100644 --- a/spec/support/shared_context/policies/project_policy_shared_context.rb +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -25,6 +25,7 @@ RSpec.shared_context 'ProjectPolicy context' do admin_issue admin_label admin_list read_commit_status read_build read_container_image read_pipeline read_environment read_deployment read_merge_request download_wiki_code read_sentry_issue read_release + read_prometheus ] end diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb index 98ab04c5636..eb051166a69 100644 --- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -4,7 +4,7 @@ shared_examples 'set sort order from user preference' do # however any other field present in user_preferences table can be used for testing. context 'when database is in read-only mode' do - it 'it does not update user preference' do + it 'does not update user preference' do allow(Gitlab::Database).to receive(:read_only?).and_return(true) expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param }) diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb index ba9b7d3bdcf..01bee603274 100644 --- a/spec/support/shared_examples/helm_generated_script.rb +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -6,7 +6,7 @@ shared_examples 'helm commands' do EOS end - it 'should return appropriate command' do + it 'returns appropriate command' do expect(subject.generate_script.strip).to eq((helm_setup + commands).strip) end end diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb index 90d67fd00fc..244f4766a84 100644 --- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb @@ -1,11 +1,11 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| include ProjectForksHelper - def get_action(action, project) + def get_action(action, project, extra_params = {}) if action - get action, params: { author_id: project.creator.id } + get action, params: { author_id: project.creator.id }.merge(extra_params) else - get :index, params: { namespace_id: project.namespace, project_id: project } + get :index, params: { namespace_id: project.namespace, project_id: project }.merge(extra_params) end end @@ -17,23 +17,44 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil| end end - before do - @issuable_ids = %w[fix improve/awesome].map do |source_branch| - create_issuable(issuable_type, project, source_branch: source_branch).id + let!(:issuables) do + %w[fix improve/awesome].map do |source_branch| + create_issuable(issuable_type, project, source_branch: source_branch) end end + let(:issuable_ids) { issuables.map(&:id) } + it "creates indexed meta-data object for issuable notes and votes count" do get_action(action, project) meta_data = assigns(:issuable_meta_data) aggregate_failures do - expect(meta_data.keys).to match_array(@issuable_ids) + expect(meta_data.keys).to match_array(issuables.map(&:id)) expect(meta_data.values).to all(be_kind_of(Issuable::IssuableMeta)) end end + context 'searching' do + let(:result_issuable) { issuables.first } + let(:search) { result_issuable.title } + + before do + stub_feature_flags(attempt_project_search_optimizations: true) + end + + # .simple_sorts is the same across all Sortable classes + sorts = ::Issue.simple_sorts.keys + %w[popularity priority label_priority] + sorts.each do |sort| + it "works when sorting by #{sort}" do + get_action(action, project, search: search, sort: sort) + + expect(assigns(:issuable_meta_data).keys).to include(result_issuable.id) + end + end + end + it "avoids N+1 queries" do control = ActiveRecord::QueryRecorder.new { get_action(action, project) } issuable = create_issuable(issuable_type, project, source_branch: 'csv') diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb index d87b3181e80..033b65bdc84 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb @@ -9,12 +9,12 @@ shared_examples 'cluster application helm specs' do |application_name| application.cluster.application_helm.ca_cert = nil end - it 'should not include cert files when there is no ca_cert entry' do + it 'does not include cert files when there is no ca_cert entry' do expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem') end end - it 'should include cert files when there is a ca_cert entry' do + it 'includes cert files when there is a ca_cert entry' do expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem') expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert) diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb index 24576fe0021..633c7135fbc 100644 --- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb @@ -1,4 +1,38 @@ # frozen_string_literal: true shared_examples 'duplicate quick action' do + context 'mark issue as duplicate' do + let(:original_issue) { create(:issue, project: project) } + + context 'when the current user can update issues' do + it 'does not create a note, and marks the issue as a duplicate' do + add_note("/duplicate ##{original_issue.to_reference}") + + expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" + expect(page).to have_content 'Commands applied' + expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" + + expect(issue.reload).to be_closed + end + end + + context 'when the current user cannot update the issue' do + let(:guest) { create(:user) } + before do + project.add_guest(guest) + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end + + it 'does not create a note, and does not mark the issue as a duplicate' do + add_note("/duplicate ##{original_issue.to_reference}") + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" + + expect(issue.reload).to be_open + end + end + end end diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb index 5904164fcfc..dd1676a08e2 100644 --- a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb @@ -1,25 +1,35 @@ # frozen_string_literal: true -shared_examples 'remove_due_date action not available' do - it 'does not remove the due date' do - add_note("/remove_due_date") +shared_examples 'remove_due_date quick action' do + context 'remove_due_date action available and due date can be removed' do + it 'removes the due date accordingly' do + add_note('/remove_due_date') - expect(page).not_to have_content 'Commands applied' - expect(page).not_to have_content '/remove_due_date' - end -end + expect(page).not_to have_content '/remove_due_date' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) -shared_examples 'remove_due_date action available and due date can be removed' do - it 'removes the due date accordingly' do - add_note('/remove_due_date') + page.within '.due_date' do + expect(page).to have_content 'No due date' + end + end + end - expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Commands applied' + context 'remove_due_date action not available' do + let(:guest) { create(:user) } + before do + project.add_guest(guest) + gitlab_sign_out + sign_in(guest) + visit project_issue_path(project, issue) + end - visit project_issue_path(project, issue) + it 'does not remove the due date' do + add_note("/remove_due_date") - page.within '.due_date' do - expect(page).to have_content 'No due date' + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/remove_due_date' end end end diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb index ccb4a85325b..cf2bdb1dd68 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb @@ -1,4 +1,81 @@ # frozen_string_literal: true shared_examples 'target_branch quick action' do + describe '/target_branch command in merge request' do + let(:another_project) { create(:project, :public, :repository) } + let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } + + before do + another_project.add_maintainer(user) + sign_in(user) + end + + it 'changes target_branch in new merge_request' do + visit project_new_merge_request_path(another_project, new_url_opts) + + fill_in "merge_request_title", with: 'My brand new feature' + fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:" + click_button "Submit merge request" + + merge_request = another_project.merge_requests.first + expect(merge_request.description).to eq "le feature \nFeature description:" + expect(merge_request.target_branch).to eq 'fix' + end + + it 'does not change target branch when merge request is edited' do + new_merge_request = create(:merge_request, source_project: another_project) + + visit edit_project_merge_request_path(another_project, new_merge_request) + fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n" + click_button "Save changes" + + new_merge_request = another_project.merge_requests.first + expect(new_merge_request.description).to include('/target_branch') + expect(new_merge_request.target_branch).not_to eq('fix') + end + end + + describe '/target_branch command from note' do + context 'when the current user can change target branch' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + end + + it 'changes target branch from a note' do + add_note("message start \n/target_branch merge-test\n message end.") + + wait_for_requests + expect(page).not_to have_content('/target_branch') + expect(page).to have_content('message start') + expect(page).to have_content('message end.') + + expect(merge_request.reload.target_branch).to eq 'merge-test' + end + + it 'does not fail when target branch does not exists' do + add_note('/target_branch totally_not_existing_branch') + + expect(page).not_to have_content('/target_branch') + + expect(merge_request.target_branch).to eq 'feature' + end + end + + context 'when current user can not change target branch' do + before do + project.add_guest(guest) + sign_in(guest) + visit project_merge_request_path(project, merge_request) + end + + it 'does not change target branch' do + add_note('/target_branch merge-test') + + expect(page).not_to have_content '/target_branch merge-test' + + expect(merge_request.target_branch).to eq 'feature' + end + end + end end diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb index 6abb12b41b2..60d6e53e74f 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb @@ -1,4 +1,47 @@ # frozen_string_literal: true shared_examples 'wip quick action' do + context 'when the current user can toggle the WIP prefix' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + wait_for_requests + end + + it 'adds the WIP: prefix to the title' do + add_note('/wip') + + expect(page).not_to have_content '/wip' + expect(page).to have_content 'Commands applied' + + expect(merge_request.reload.work_in_progress?).to eq true + end + + it 'removes the WIP: prefix from the title' do + merge_request.update!(title: merge_request.wip_title) + add_note('/wip') + + expect(page).not_to have_content '/wip' + expect(page).to have_content 'Commands applied' + + expect(merge_request.reload.work_in_progress?).to eq false + end + end + + context 'when the current user cannot toggle the WIP prefix' do + before do + project.add_guest(guest) + sign_in(guest) + visit project_merge_request_path(project, merge_request) + end + + it 'does not change the WIP prefix' do + add_note('/wip') + + expect(page).not_to have_content '/wip' + expect(page).not_to have_content 'Commands applied' + + expect(merge_request.reload.work_in_progress?).to eq false + end + end end diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb index 4f662db2120..833c31a57cb 100644 --- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb @@ -220,11 +220,11 @@ RSpec.shared_examples 'snippet visibility' do end context "For #{params[:project_type]} project and #{params[:user_type]} users" do - it 'should agree with the read_project_snippet policy' do + it 'agrees with the read_project_snippet policy' do expect(can?(user, :read_project_snippet, snippet)).to eq(outcome) end - it 'should return proper outcome' do + it 'returns proper outcome' do results = described_class.new(user, project: project).execute expect(results.include?(snippet)).to eq(outcome) @@ -232,7 +232,7 @@ RSpec.shared_examples 'snippet visibility' do end context "Without a given project and #{params[:user_type]} users" do - it 'should return proper outcome' do + it 'returns proper outcome' do results = described_class.new(user).execute expect(results.include?(snippet)).to eq(outcome) end @@ -283,16 +283,16 @@ RSpec.shared_examples 'snippet visibility' do let!(:snippet) { create(:personal_snippet, visibility_level: snippet_visibility, author: author) } context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do - it 'should agree with read_personal_snippet policy' do + it 'agrees with read_personal_snippet policy' do expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome) end - it 'should return proper outcome' do + it 'returns proper outcome' do results = described_class.new(user).execute expect(results.include?(snippet)).to eq(outcome) end - it 'should return personal snippets when the user cannot read cross project' do + it 'returns personal snippets when the user cannot read cross project' do allow(Ability).to receive(:allowed?).and_call_original allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb index ab98976ec27..42352f9b9f8 100644 --- a/spec/uploaders/records_uploads_spec.rb +++ b/spec/uploaders/records_uploads_spec.rb @@ -71,7 +71,7 @@ describe RecordsUploads do expect { uploader.store!(upload_fixture('rails_sample.jpg')) }.not_to change { Upload.count } end - it 'it destroys Upload records at the same path before recording' do + it 'destroys Upload records at the same path before recording' do existing = Upload.create!( path: File.join('uploads', 'rails_sample.jpg'), size: 512.kilobytes, @@ -88,7 +88,7 @@ describe RecordsUploads do end describe '#destroy_upload callback' do - it 'it destroys Upload records at the same path after removal' do + it 'destroys Upload records at the same path after removal' do uploader.store!(upload_fixture('rails_sample.jpg')) expect { uploader.remove! }.to change { Upload.count }.from(1).to(0) diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb index 38cfb84f0d5..29e15960fb8 100644 --- a/spec/views/groups/edit.html.haml_spec.rb +++ b/spec/views/groups/edit.html.haml_spec.rb @@ -12,7 +12,7 @@ describe 'groups/edit.html.haml' do end shared_examples_for '"Share with group lock" setting' do |checkbox_options| - it 'should have the correct label, help text, and checkbox options' do + it 'has the correct label, help text, and checkbox options' do assign(:group, test_group) allow(view).to receive(:can?).with(test_user, :admin_group, test_group).and_return(true) allow(view).to receive(:can_change_group_visibility_level?).and_return(false) diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index 908ecb898e4..12925a5ab07 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -45,7 +45,7 @@ describe 'projects/_home_panel' do context 'badges' do shared_examples 'show badges' do - it 'should render the all badges' do + it 'renders the all badges' do render expect(rendered).to have_selector('.project-badges a') @@ -70,7 +70,7 @@ describe 'projects/_home_panel' do context 'has no badges' do let(:project) { create(:project) } - it 'should not render any badge' do + it 'does not render any badge' do render expect(rendered).not_to have_selector('.project-badges') diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index b52fc719a64..ff2d491539b 100644 --- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -13,4 +13,14 @@ describe 'projects/settings/ci_cd/_autodevops_form' do expect(rendered).to have_text('You must add a Kubernetes cluster integration to this project with a domain in order for your deployment strategy to work correctly.') end + + context 'when the project has an available kubernetes cluster' do + let!(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) } + + it 'does not show a warning message' do + render + + expect(rendered).not_to have_text('You must add a Kubernetes cluster') + end + end end diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml.rb index 4769d569548..cbbb984935f 100644 --- a/spec/views/shared/milestones/_issuables.html.haml.rb +++ b/spec/views/shared/milestones/_issuables.html.haml.rb @@ -11,12 +11,12 @@ describe 'shared/milestones/_issuables.html.haml' do stub_template 'shared/milestones/_issuable.html.haml' => '' end - it 'should show the issuables count if show_counter is true' do + it 'shows the issuables count if show_counter is true' do render 'shared/milestones/issuables', show_counter: true expect(rendered).to have_content('100') end - it 'should not show the issuables count if show_counter is false' do + it 'does not show the issuables count if show_counter is false' do render 'shared/milestones/issuables', show_counter: false expect(rendered).not_to have_content('100') end @@ -24,7 +24,7 @@ describe 'shared/milestones/_issuables.html.haml' do describe 'a high issuables count' do let(:issuables_size) { 1000 } - it 'should show a delimited number if show_counter is true' do + it 'shows a delimited number if show_counter is true' do render 'shared/milestones/issuables', show_counter: true expect(rendered).to have_content('1,000') end diff --git a/spec/views/shared/projects/_project.html.haml_spec.rb b/spec/views/shared/projects/_project.html.haml_spec.rb index 3b14045e61f..dc223861037 100644 --- a/spec/views/shared/projects/_project.html.haml_spec.rb +++ b/spec/views/shared/projects/_project.html.haml_spec.rb @@ -8,13 +8,13 @@ describe 'shared/projects/_project.html.haml' do allow(view).to receive(:can?) { true } end - it 'should render creator avatar if project has a creator' do + it 'renders creator avatar if project has a creator' do render 'shared/projects/project', use_creator_avatar: true, project: project expect(rendered).to have_selector('img.avatar') end - it 'should render a generic avatar if project does not have a creator' do + it 'renders a generic avatar if project does not have a creator' do project.creator = nil render 'shared/projects/project', use_creator_avatar: true, project: project |