diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-03 18:06:49 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-03 18:06:49 +0000 |
commit | ab7cf450ba19cf80b9534f25dc707b33845e3014 (patch) | |
tree | bbfa6aba83c48aea68d79c4179ce576b6eec326d /spec | |
parent | 4204cf308596e0e26f578a6e2da88f49c0f4aad9 (diff) | |
download | gitlab-ce-ab7cf450ba19cf80b9534f25dc707b33845e3014.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
44 files changed, 1162 insertions, 755 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 4a10e7b5325..04bbffc587f 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -90,14 +90,6 @@ describe ApplicationController do let(:format) { :html } it_behaves_like 'setting gon variables' - - context 'for peek requests' do - before do - request.path = '/-/peek' - end - - it_behaves_like 'not setting gon variables' - end end context 'with json format' do @@ -105,6 +97,12 @@ describe ApplicationController do it_behaves_like 'not setting gon variables' end + + context 'with atom format' do + let(:format) { :atom } + + it_behaves_like 'not setting gon variables' + end end describe 'session expiration' do diff --git a/spec/controllers/instance_statistics/conversational_development_index_controller_spec.rb b/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb index 4935cb265bf..5825c6295f6 100644 --- a/spec/controllers/instance_statistics/conversational_development_index_controller_spec.rb +++ b/spec/controllers/instance_statistics/dev_ops_score_controller_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -describe InstanceStatistics::ConversationalDevelopmentIndexController do +describe InstanceStatistics::DevOpsScoreController do it_behaves_like 'instance statistics availability' end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index affe0e0f970..4f8ab6a5def 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -178,7 +178,7 @@ describe Projects::BranchesController do it 'redirects to newly created branch' do result = { status: :success, branch: double(name: branch) } - expect_any_instance_of(CreateBranchService).to receive(:execute).and_return(result) + expect_any_instance_of(::Branches::CreateService).to receive(:execute).and_return(result) expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) post :create, @@ -200,7 +200,7 @@ describe Projects::BranchesController do it 'redirects to autodeploy setup page' do result = { status: :success, branch: double(name: branch) } - expect_any_instance_of(CreateBranchService).to receive(:execute).and_return(result) + expect_any_instance_of(::Branches::CreateService).to receive(:execute).and_return(result) expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) post :create, @@ -221,7 +221,7 @@ describe Projects::BranchesController do create(:cluster, :provided_by_gcp, projects: [project]) - expect_any_instance_of(CreateBranchService).to receive(:execute).and_return(result) + expect_any_instance_of(::Branches::CreateService).to receive(:execute).and_return(result) expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) post :create, @@ -459,7 +459,7 @@ describe Projects::BranchesController do end it 'starts worker to delete merged branches' do - expect_any_instance_of(DeleteMergedBranchesService).to receive(:async_execute) + expect_any_instance_of(::Branches::DeleteMergedService).to receive(:async_execute) destroy_all_merged end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 1bcf3bb106b..f35babc1b56 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -228,10 +228,10 @@ describe UploadsController do user.block end - it "redirects to the sign in page" do + it "responds with status 401" do get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" } - expect(response).to redirect_to(new_user_session_path) + expect(response).to have_gitlab_http_status(401) end end @@ -320,10 +320,10 @@ describe UploadsController do end context "when not signed in" do - it "redirects to the sign in page" do + it "responds with status 401" do get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } - expect(response).to redirect_to(new_user_session_path) + expect(response).to have_gitlab_http_status(401) end end @@ -343,10 +343,10 @@ describe UploadsController do project.add_maintainer(user) end - it "redirects to the sign in page" do + it "responds with status 401" do get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } - expect(response).to redirect_to(new_user_session_path) + expect(response).to have_gitlab_http_status(401) end end @@ -439,10 +439,10 @@ describe UploadsController do user.block end - it "redirects to the sign in page" do + it "responds with status 401" do get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } - expect(response).to redirect_to(new_user_session_path) + expect(response).to have_gitlab_http_status(401) end end @@ -526,10 +526,10 @@ describe UploadsController do end context "when not signed in" do - it "redirects to the sign in page" do + it "responds with status 401" do get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } - expect(response).to redirect_to(new_user_session_path) + expect(response).to have_gitlab_http_status(401) end end @@ -549,10 +549,10 @@ describe UploadsController do project.add_maintainer(user) end - it "redirects to the sign in page" do + it "responds with status 401" do get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } - expect(response).to redirect_to(new_user_session_path) + expect(response).to have_gitlab_http_status(401) end end diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb index c21bc922de7..4ad19710d90 100644 --- a/spec/features/dashboard/milestones_spec.rb +++ b/spec/features/dashboard/milestones_spec.rb @@ -30,6 +30,7 @@ describe 'Dashboard > Milestones' do expect(current_path).to eq dashboard_milestones_path expect(page).to have_content(milestone.title) expect(page).to have_content(group.name) + expect(first('.milestone')).to have_content('Merge Requests') end describe 'new milestones dropdown', :js do @@ -46,4 +47,23 @@ describe 'Dashboard > Milestones' do end end end + + describe 'with merge requests disabled' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, :merge_requests_disabled, namespace: user.namespace) } + let!(:milestone) { create(:milestone, project: project) } + + before do + group.add_developer(user) + sign_in(user) + visit dashboard_milestones_path + end + + it 'does not see milestones' do + expect(current_path).to eq dashboard_milestones_path + expect(page).to have_content(milestone.title) + expect(first('.milestone')).to have_no_content('Merge Requests') + end + end end diff --git a/spec/features/instance_statistics/conversational_development_index_spec.rb b/spec/features/instance_statistics/dev_ops_score_spec.rb index 6d05682fcd5..c9e6ab67267 100644 --- a/spec/features/instance_statistics/conversational_development_index_spec.rb +++ b/spec/features/instance_statistics/dev_ops_score_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' -describe 'Conversational Development Index' do +describe 'Dev Ops Score' do before do sign_in(create(:admin)) end it 'has dismissable intro callout', :js do - visit instance_statistics_conversational_development_index_index_path + visit instance_statistics_dev_ops_score_index_path expect(page).to have_content 'Introducing Your Conversational Development Index' @@ -23,13 +23,13 @@ describe 'Conversational Development Index' do end it 'shows empty state' do - visit instance_statistics_conversational_development_index_index_path + visit instance_statistics_dev_ops_score_index_path expect(page).to have_content('Usage ping is not enabled') end it 'hides the intro callout' do - visit instance_statistics_conversational_development_index_index_path + visit instance_statistics_dev_ops_score_index_path expect(page).not_to have_content 'Introducing Your Conversational Development Index' end @@ -39,7 +39,7 @@ describe 'Conversational Development Index' do it 'shows empty state' do stub_application_setting(usage_ping_enabled: true) - visit instance_statistics_conversational_development_index_index_path + visit instance_statistics_dev_ops_score_index_path expect(page).to have_content('Data is still calculating') end @@ -50,7 +50,7 @@ describe 'Conversational Development Index' do stub_application_setting(usage_ping_enabled: true) create(:dev_ops_score_metric) - visit instance_statistics_conversational_development_index_index_path + visit instance_statistics_dev_ops_score_index_path expect(page).to have_content( 'Issues created per active user 1.2 You 9.3 Lead 13.3%' diff --git a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb index 224261dec00..9ef6847f7f5 100644 --- a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb +++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb @@ -9,7 +9,7 @@ describe 'Merge request > User sees deleted target branch', :js do before do project.add_maintainer(user) - DeleteBranchService.new(project, user).execute('feature') + ::Branches::DeleteService.new(project, user).execute('feature') sign_in(user) visit project_merge_request_path(project, merge_request) end diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index c42eb8560a4..22b2ea81b32 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -178,10 +178,11 @@ describe 'Merge request > User selects branches for new MR', :js do end context 'with special characters in branch names' do + let(:create_branch_service) { ::Branches::CreateService.new(project, user) } + it 'escapes quotes in branch names' do special_branch_name = '"with-quotes"' - CreateBranchService.new(project, user) - .execute(special_branch_name, 'add-pdf-file') + create_branch_service.execute(special_branch_name, 'add-pdf-file') visit project_new_merge_request_path(project) select_source_branch(special_branch_name) @@ -192,8 +193,7 @@ describe 'Merge request > User selects branches for new MR', :js do it 'does not escape unicode in branch names' do special_branch_name = 'ʕ•ᴥ•ʔ' - CreateBranchService.new(project, user) - .execute(special_branch_name, 'add-pdf-file') + create_branch_service.execute(special_branch_name, 'add-pdf-file') visit project_new_merge_request_path(project) select_source_branch(special_branch_name) diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb index 09378cab5e3..c91fe95aa77 100644 --- a/spec/features/milestones/user_views_milestones_spec.rb +++ b/spec/features/milestones/user_views_milestones_spec.rb @@ -18,6 +18,7 @@ describe "User views milestones" do expect(page).to have_content(milestone.title) .and have_content(milestone.expires_at) .and have_content("Issues") + .and have_content("Merge Requests") end context "with issues" do @@ -32,6 +33,7 @@ describe "User views milestones" do .and have_selector("#tab-issues li.issuable-row", count: 2) .and have_content(issue.title) .and have_content(closed_issue.title) + .and have_selector("#tab-merge-requests") end end @@ -62,3 +64,32 @@ describe "User views milestones" do end end end + +describe "User views milestones with no MR" do + set(:user) { create(:user) } + set(:project) { create(:project, :merge_requests_disabled) } + set(:milestone) { create(:milestone, project: project) } + + before do + project.add_developer(user) + sign_in(user) + + visit(project_milestones_path(project)) + end + + it "shows milestone" do + expect(page).to have_content(milestone.title) + .and have_content(milestone.expires_at) + .and have_content("Issues") + .and have_no_content("Merge Requests") + end + + it "opens milestone" do + click_link(milestone.title) + + expect(current_path).to eq(project_milestone_path(project, milestone)) + expect(page).to have_content(milestone.title) + .and have_selector("#tab-issues") + .and have_no_selector("#tab-merge-requests") + end +end diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index 3c6553f3547..cd4fae60049 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -31,6 +31,7 @@ class CustomEnvironment extends JSDOMEnvironment { this.global.gon = { ee: IS_EE, }; + this.global.IS_EE = IS_EE; this.rejectedPromises = []; diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index 758e86235be..a184892773b 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -1,5 +1,10 @@ +// This import path needs to be relative for now because this mock data is used in +// Karma specs too, where the helpers/test_constants alias can not be resolved +import { TEST_HOST } from '../helpers/test_constants'; + export const mockHost = 'http://test.host'; export const mockProjectDir = '/frontend-fixtures/environments-project'; +export const mockApiEndpoint = `${TEST_HOST}/monitoring/mock`; export const anomalyDeploymentData = [ { @@ -278,6 +283,49 @@ export const mockedQueryResultPayload = { ], }; +export const mockedQueryResultPayloadCoresTotal = { + metricId: '13_system_metrics_kubernetes_container_cores_total', + result: [ + { + metric: {}, + values: [ + [1563272065.589, '9.396484375'], + [1563272125.589, '9.333984375'], + [1563272185.589, '9.333984375'], + [1563272245.589, '9.333984375'], + [1563272305.589, '9.333984375'], + [1563272365.589, '9.333984375'], + [1563272425.589, '9.38671875'], + [1563272485.589, '9.333984375'], + [1563272545.589, '9.333984375'], + [1563272605.589, '9.333984375'], + [1563272665.589, '9.333984375'], + [1563272725.589, '9.333984375'], + [1563272785.589, '9.396484375'], + [1563272845.589, '9.333984375'], + [1563272905.589, '9.333984375'], + [1563272965.589, '9.3984375'], + [1563273025.589, '9.337890625'], + [1563273085.589, '9.34765625'], + [1563273145.589, '9.337890625'], + [1563273205.589, '9.337890625'], + [1563273265.589, '9.337890625'], + [1563273325.589, '9.337890625'], + [1563273385.589, '9.337890625'], + [1563273445.589, '9.337890625'], + [1563273505.589, '9.337890625'], + [1563273565.589, '9.337890625'], + [1563273625.589, '9.337890625'], + [1563273685.589, '9.337890625'], + [1563273745.589, '9.337890625'], + [1563273805.589, '9.337890625'], + [1563273865.589, '9.390625'], + [1563273925.589, '9.390625'], + ], + }, + ], +}; + export const metricsGroupsAPIResponse = [ { group: 'System metrics (Kubernetes)', @@ -460,3 +508,130 @@ export const dashboardGitResponse = [ path: '.gitlab/dashboards/dashboard_2.yml', }, ]; + +export const graphDataPrometheusQuery = { + title: 'Super Chart A2', + type: 'single-stat', + weight: 2, + metrics: [ + { + id: 'metric_a1', + metricId: '2', + query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024', + unit: 'MB', + label: 'Total Consumption', + metric_id: 2, + prometheus_endpoint_path: + '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', + result: [ + { + metric: { job: 'prometheus' }, + value: ['2019-06-26T21:03:20.881Z', 91], + }, + ], + }, + ], +}; + +export const graphDataPrometheusQueryRange = { + title: 'Super Chart A1', + type: 'area-chart', + weight: 2, + metrics: [ + { + id: 'metric_a1', + metricId: '2', + query_range: + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + unit: 'MB', + label: 'Total Consumption', + metric_id: 2, + prometheus_endpoint_path: + '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', + result: [ + { + metric: {}, + values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']], + }, + ], + }, + ], +}; + +export const graphDataPrometheusQueryRangeMultiTrack = { + title: 'Super Chart A3', + type: 'heatmap', + weight: 3, + x_label: 'Status Code', + y_label: 'Time', + metrics: [ + { + metricId: '1', + id: 'response_metrics_nginx_ingress_throughput_status_code', + query_range: + 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)', + unit: 'req / sec', + label: 'Status Code', + metric_id: 1, + prometheus_endpoint_path: + '/root/rails_nodb/environments/3/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29', + result: [ + { + metric: { status_code: '1xx' }, + values: [ + ['2019-08-30T15:00:00.000Z', 0], + ['2019-08-30T16:00:00.000Z', 2], + ['2019-08-30T17:00:00.000Z', 0], + ['2019-08-30T18:00:00.000Z', 0], + ['2019-08-30T19:00:00.000Z', 0], + ['2019-08-30T20:00:00.000Z', 3], + ], + }, + { + metric: { status_code: '2xx' }, + values: [ + ['2019-08-30T15:00:00.000Z', 1], + ['2019-08-30T16:00:00.000Z', 3], + ['2019-08-30T17:00:00.000Z', 6], + ['2019-08-30T18:00:00.000Z', 10], + ['2019-08-30T19:00:00.000Z', 8], + ['2019-08-30T20:00:00.000Z', 6], + ], + }, + { + metric: { status_code: '3xx' }, + values: [ + ['2019-08-30T15:00:00.000Z', 1], + ['2019-08-30T16:00:00.000Z', 2], + ['2019-08-30T17:00:00.000Z', 3], + ['2019-08-30T18:00:00.000Z', 3], + ['2019-08-30T19:00:00.000Z', 2], + ['2019-08-30T20:00:00.000Z', 1], + ], + }, + { + metric: { status_code: '4xx' }, + values: [ + ['2019-08-30T15:00:00.000Z', 2], + ['2019-08-30T16:00:00.000Z', 0], + ['2019-08-30T17:00:00.000Z', 0], + ['2019-08-30T18:00:00.000Z', 2], + ['2019-08-30T19:00:00.000Z', 0], + ['2019-08-30T20:00:00.000Z', 2], + ], + }, + { + metric: { status_code: '5xx' }, + values: [ + ['2019-08-30T15:00:00.000Z', 0], + ['2019-08-30T16:00:00.000Z', 1], + ['2019-08-30T17:00:00.000Z', 0], + ['2019-08-30T18:00:00.000Z', 0], + ['2019-08-30T19:00:00.000Z', 0], + ['2019-08-30T20:00:00.000Z', 2], + ], + }, + ], + }, + ], +}; diff --git a/spec/frontend/sidebar/assignees_spec.js b/spec/frontend/sidebar/assignees_spec.js new file mode 100644 index 00000000000..14b6da10991 --- /dev/null +++ b/spec/frontend/sidebar/assignees_spec.js @@ -0,0 +1,200 @@ +import { mount } from '@vue/test-utils'; +import { trimText } from 'helpers/text_helper'; +import Assignee from '~/sidebar/components/assignees/assignees.vue'; +import UsersMock from './mock_data'; +import UsersMockHelper from '../helpers/user_mock_data_helper'; + +describe('Assignee component', () => { + const getDefaultProps = () => ({ + rootPath: 'http://localhost:3000', + users: [], + editable: false, + }); + let wrapper; + + const createWrapper = (propsData = getDefaultProps()) => { + wrapper = mount(Assignee, { + propsData, + sync: false, + attachToDocument: true, + }); + }; + + const findComponentTextNoUsers = () => wrapper.find('.assign-yourself'); + const findCollapsedChildren = () => wrapper.findAll('.sidebar-collapsed-icon > *'); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('No assignees/users', () => { + it('displays no assignee icon when collapsed', () => { + createWrapper(); + const collapsedChildren = findCollapsedChildren(); + + expect(collapsedChildren.length).toBe(1); + expect(collapsedChildren.at(0).attributes('aria-label')).toBe('None'); + expect(collapsedChildren.at(0).classes()).toContain('fa', 'fa-user'); + }); + + it('displays only "None" when no users are assigned and the issue is read-only', () => { + createWrapper(); + const componentTextNoUsers = trimText(findComponentTextNoUsers().text()); + + expect(componentTextNoUsers).toBe('None'); + expect(componentTextNoUsers).not.toContain('assign yourself'); + }); + + it('displays only "None" when no users are assigned and the issue can be edited', () => { + createWrapper({ + ...getDefaultProps(), + editable: true, + }); + const componentTextNoUsers = trimText(findComponentTextNoUsers().text()); + + expect(componentTextNoUsers).toContain('None'); + expect(componentTextNoUsers).toContain('assign yourself'); + }); + + it('emits the assign-self event when "assign yourself" is clicked', () => { + createWrapper({ + ...getDefaultProps(), + editable: true, + }); + + jest.spyOn(wrapper.vm, '$emit'); + wrapper.find('.assign-yourself .btn-link').trigger('click'); + + expect(wrapper.emitted('assign-self')).toBeTruthy(); + }); + }); + + describe('One assignee/user', () => { + it('displays one assignee icon when collapsed', () => { + createWrapper({ + ...getDefaultProps(), + users: [UsersMock.user], + }); + + const collapsedChildren = findCollapsedChildren(); + const assignee = collapsedChildren.at(0); + + expect(collapsedChildren.length).toBe(1); + expect(assignee.find('.avatar').attributes('src')).toBe(UsersMock.user.avatar); + expect(assignee.find('.avatar').attributes('alt')).toBe(`${UsersMock.user.name}'s avatar`); + + expect(trimText(assignee.find('.author').text())).toBe(UsersMock.user.name); + }); + }); + + describe('Two or more assignees/users', () => { + it('displays two assignee icons when collapsed', () => { + const users = UsersMockHelper.createNumberRandomUsers(2); + createWrapper({ + ...getDefaultProps(), + users, + }); + + const collapsedChildren = findCollapsedChildren(); + + expect(collapsedChildren.length).toBe(2); + + const first = collapsedChildren.at(0); + + expect(first.find('.avatar').attributes('src')).toBe(users[0].avatar); + expect(first.find('.avatar').attributes('alt')).toBe(`${users[0].name}'s avatar`); + + expect(trimText(first.find('.author').text())).toBe(users[0].name); + + const second = collapsedChildren.at(1); + + expect(second.find('.avatar').attributes('src')).toBe(users[1].avatar); + expect(second.find('.avatar').attributes('alt')).toBe(`${users[1].name}'s avatar`); + + expect(trimText(second.find('.author').text())).toBe(users[1].name); + }); + + it('displays one assignee icon and counter when collapsed', () => { + const users = UsersMockHelper.createNumberRandomUsers(3); + createWrapper({ + ...getDefaultProps(), + users, + }); + + const collapsedChildren = findCollapsedChildren(); + + expect(collapsedChildren.length).toBe(2); + + const first = collapsedChildren.at(0); + + expect(first.find('.avatar').attributes('src')).toBe(users[0].avatar); + expect(first.find('.avatar').attributes('alt')).toBe(`${users[0].name}'s avatar`); + + expect(trimText(first.find('.author').text())).toBe(users[0].name); + + const second = collapsedChildren.at(1); + + expect(trimText(second.find('.avatar-counter').text())).toBe('+2'); + }); + + it('Shows two assignees', () => { + const users = UsersMockHelper.createNumberRandomUsers(2); + createWrapper({ + ...getDefaultProps(), + users, + editable: true, + }); + + expect(wrapper.findAll('.user-item').length).toBe(users.length); + expect(wrapper.find('.user-list-more').exists()).toBe(false); + }); + + it('shows sorted assignee where "can merge" users are sorted first', () => { + const users = UsersMockHelper.createNumberRandomUsers(3); + users[0].can_merge = false; + users[1].can_merge = false; + users[2].can_merge = true; + + createWrapper({ + ...getDefaultProps(), + users, + editable: true, + }); + + expect(wrapper.vm.sortedAssigness[0].can_merge).toBe(true); + }); + + it('passes the sorted assignees to the uncollapsed-assignee-list', () => { + const users = UsersMockHelper.createNumberRandomUsers(3); + users[0].can_merge = false; + users[1].can_merge = false; + users[2].can_merge = true; + + createWrapper({ + ...getDefaultProps(), + users, + }); + + const userItems = wrapper.findAll('.user-list .user-item a'); + + expect(userItems.length).toBe(3); + expect(userItems.at(0).attributes('data-original-title')).toBe(users[2].name); + }); + + it('passes the sorted assignees to the collapsed-assignee-list', () => { + const users = UsersMockHelper.createNumberRandomUsers(3); + users[0].can_merge = false; + users[1].can_merge = false; + users[2].can_merge = true; + + createWrapper({ + ...getDefaultProps(), + users, + }); + + const collapsedButton = wrapper.find('.sidebar-collapsed-user button'); + + expect(trimText(collapsedButton.text())).toBe(users[2].name); + }); + }); +}); diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js new file mode 100644 index 00000000000..3ee97b978fd --- /dev/null +++ b/spec/frontend/sidebar/mock_data.js @@ -0,0 +1,213 @@ +const RESPONSE_MAP = { + GET: { + '/gitlab-org/gitlab-shell/issues/5.json': { + id: 45, + iid: 5, + author_id: 23, + description: 'Nulla ullam commodi delectus adipisci quis sit.', + lock_version: null, + milestone_id: 21, + position: 0, + state: 'closed', + title: 'Vel et nulla voluptatibus corporis dolor iste saepe laborum.', + updated_by_id: 1, + created_at: '2017-02-02T21: 49: 49.664Z', + updated_at: '2017-05-03T22: 26: 03.760Z', + time_estimate: 0, + total_time_spent: 0, + human_time_estimate: null, + human_total_time_spent: null, + branch_name: null, + confidential: false, + assignees: [ + { + name: 'User 0', + username: 'user0', + id: 22, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/user0', + }, + { + name: 'Marguerite Bartell', + username: 'tajuana', + id: 18, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/tajuana', + }, + { + name: 'Laureen Ritchie', + username: 'michaele.will', + id: 16, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', + web_url: 'http: //localhost:3001/michaele.will', + }, + ], + due_date: null, + moved_to_id: null, + project_id: 4, + weight: null, + milestone: { + id: 21, + iid: 1, + project_id: 4, + title: 'v0.0', + description: 'Molestiae commodi laboriosam odio sunt eaque reprehenderit.', + state: 'active', + created_at: '2017-02-02T21: 49: 30.530Z', + updated_at: '2017-02-02T21: 49: 30.530Z', + due_date: null, + start_date: null, + }, + labels: [], + }, + '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras': { + assignees: [ + { + name: 'User 0', + username: 'user0', + id: 22, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', + web_url: 'http://localhost:3001/user0', + }, + { + name: 'Marguerite Bartell', + username: 'tajuana', + id: 18, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', + web_url: 'http://localhost:3001/tajuana', + }, + { + name: 'Laureen Ritchie', + username: 'michaele.will', + id: 16, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', + web_url: 'http://localhost:3001/michaele.will', + }, + ], + human_time_estimate: null, + human_total_time_spent: null, + participants: [ + { + name: 'User 0', + username: 'user0', + id: 22, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', + web_url: 'http://localhost:3001/user0', + }, + { + name: 'Marguerite Bartell', + username: 'tajuana', + id: 18, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', + web_url: 'http://localhost:3001/tajuana', + }, + { + name: 'Laureen Ritchie', + username: 'michaele.will', + id: 16, + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', + web_url: 'http://localhost:3001/michaele.will', + }, + ], + subscribed: true, + time_estimate: 0, + total_time_spent: 0, + }, + '/autocomplete/projects?project_id=15': [ + { + id: 0, + name_with_namespace: 'No project', + }, + { + id: 20, + name_with_namespace: '<img src=x onerror=alert(document.domain)> foo / bar', + }, + ], + }, + PUT: { + '/gitlab-org/gitlab-shell/issues/5.json': { + data: {}, + }, + }, + POST: { + '/gitlab-org/gitlab-shell/issues/5/move': { + id: 123, + iid: 5, + author_id: 1, + description: 'some description', + lock_version: 5, + milestone_id: null, + state: 'opened', + title: 'some title', + updated_by_id: 1, + created_at: '2017-06-27T19:54:42.437Z', + updated_at: '2017-08-18T03:39:49.222Z', + time_estimate: 0, + total_time_spent: 0, + human_time_estimate: null, + human_total_time_spent: null, + branch_name: null, + confidential: false, + assignees: [], + due_date: null, + moved_to_id: null, + project_id: 7, + milestone: null, + labels: [], + web_url: '/root/some-project/issues/5', + }, + '/gitlab-org/gitlab-shell/issues/5/toggle_subscription': {}, + }, +}; + +const mockData = { + responseMap: RESPONSE_MAP, + mediator: { + endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras', + toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', + moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', + projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', + editable: true, + currentUser: { + id: 1, + name: 'Administrator', + username: 'root', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + }, + rootPath: '/', + fullPath: '/gitlab-org/gitlab-shell', + }, + time: { + time_estimate: 3600, + total_time_spent: 0, + human_time_estimate: '1h', + human_total_time_spent: null, + }, + user: { + avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: 1, + name: 'Administrator', + username: 'root', + }, +}; + +export default mockData; diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb new file mode 100644 index 00000000000..05b787eb5ca --- /dev/null +++ b/spec/graphql/mutations/issues/set_confidential_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mutations::Issues::SetConfidential do + let(:issue) { create(:issue) } + let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } + + describe '#resolve' do + let(:confidential) { true } + let(:mutated_issue) { subject[:issue] } + subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, confidential: confidential) } + + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + + context 'when the user can update the issue' do + before do + issue.project.add_developer(user) + end + + it 'returns the issue as confidential' do + expect(mutated_issue).to eq(issue) + expect(mutated_issue.confidential).to be_truthy + expect(subject[:errors]).to be_empty + end + + context 'when passing confidential as false' do + let(:confidential) { false } + + it 'updates the issue confidentiality to false' do + expect(mutated_issue.confidential).to be_falsey + end + end + end + end +end diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index f59c4ee4264..c80401e8c1d 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -1,226 +1,5 @@ -import { - anomalyMockGraphData as importedAnomalyMockGraphData, - metricsGroupsAPIResponse as importedMetricsGroupsAPIResponse, - environmentData as importedEnvironmentData, - dashboardGitResponse as importedDashboardGitResponse, -} from '../../frontend/monitoring/mock_data'; +// No new code should be added to this file. Instead, modify the +// file this one re-exports from. For more detail about why, see: +// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349 -export const anomalyMockGraphData = importedAnomalyMockGraphData; -export const metricsGroupsAPIResponse = importedMetricsGroupsAPIResponse; -export const environmentData = importedEnvironmentData; -export const dashboardGitResponse = importedDashboardGitResponse; - -export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`; - -export const mockedQueryResultPayload = { - metricId: '17_system_metrics_kubernetes_container_memory_average', - result: [ - { - metric: {}, - values: [ - [1563272065.589, '10.396484375'], - [1563272125.589, '10.333984375'], - [1563272185.589, '10.333984375'], - [1563272245.589, '10.333984375'], - [1563272305.589, '10.333984375'], - [1563272365.589, '10.333984375'], - [1563272425.589, '10.38671875'], - [1563272485.589, '10.333984375'], - [1563272545.589, '10.333984375'], - [1563272605.589, '10.333984375'], - [1563272665.589, '10.333984375'], - [1563272725.589, '10.333984375'], - [1563272785.589, '10.396484375'], - [1563272845.589, '10.333984375'], - [1563272905.589, '10.333984375'], - [1563272965.589, '10.3984375'], - [1563273025.589, '10.337890625'], - [1563273085.589, '10.34765625'], - [1563273145.589, '10.337890625'], - [1563273205.589, '10.337890625'], - [1563273265.589, '10.337890625'], - [1563273325.589, '10.337890625'], - [1563273385.589, '10.337890625'], - [1563273445.589, '10.337890625'], - [1563273505.589, '10.337890625'], - [1563273565.589, '10.337890625'], - [1563273625.589, '10.337890625'], - [1563273685.589, '10.337890625'], - [1563273745.589, '10.337890625'], - [1563273805.589, '10.337890625'], - [1563273865.589, '10.390625'], - [1563273925.589, '10.390625'], - ], - }, - ], -}; - -export const mockedQueryResultPayloadCoresTotal = { - metricId: '13_system_metrics_kubernetes_container_cores_total', - result: [ - { - metric: {}, - values: [ - [1563272065.589, '9.396484375'], - [1563272125.589, '9.333984375'], - [1563272185.589, '9.333984375'], - [1563272245.589, '9.333984375'], - [1563272305.589, '9.333984375'], - [1563272365.589, '9.333984375'], - [1563272425.589, '9.38671875'], - [1563272485.589, '9.333984375'], - [1563272545.589, '9.333984375'], - [1563272605.589, '9.333984375'], - [1563272665.589, '9.333984375'], - [1563272725.589, '9.333984375'], - [1563272785.589, '9.396484375'], - [1563272845.589, '9.333984375'], - [1563272905.589, '9.333984375'], - [1563272965.589, '9.3984375'], - [1563273025.589, '9.337890625'], - [1563273085.589, '9.34765625'], - [1563273145.589, '9.337890625'], - [1563273205.589, '9.337890625'], - [1563273265.589, '9.337890625'], - [1563273325.589, '9.337890625'], - [1563273385.589, '9.337890625'], - [1563273445.589, '9.337890625'], - [1563273505.589, '9.337890625'], - [1563273565.589, '9.337890625'], - [1563273625.589, '9.337890625'], - [1563273685.589, '9.337890625'], - [1563273745.589, '9.337890625'], - [1563273805.589, '9.337890625'], - [1563273865.589, '9.390625'], - [1563273925.589, '9.390625'], - ], - }, - ], -}; - -export const graphDataPrometheusQuery = { - title: 'Super Chart A2', - type: 'single-stat', - weight: 2, - metrics: [ - { - id: 'metric_a1', - metricId: '2', - query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024', - unit: 'MB', - label: 'Total Consumption', - metric_id: 2, - prometheus_endpoint_path: - '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', - result: [ - { - metric: { job: 'prometheus' }, - value: ['2019-06-26T21:03:20.881Z', 91], - }, - ], - }, - ], -}; - -export const graphDataPrometheusQueryRange = { - title: 'Super Chart A1', - type: 'area-chart', - weight: 2, - metrics: [ - { - id: 'metric_a1', - metricId: '2', - query_range: - 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', - unit: 'MB', - label: 'Total Consumption', - metric_id: 2, - prometheus_endpoint_path: - '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', - result: [ - { - metric: {}, - values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']], - }, - ], - }, - ], -}; - -export const graphDataPrometheusQueryRangeMultiTrack = { - title: 'Super Chart A3', - type: 'heatmap', - weight: 3, - x_label: 'Status Code', - y_label: 'Time', - metrics: [ - { - metricId: '1', - id: 'response_metrics_nginx_ingress_throughput_status_code', - query_range: - 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)', - unit: 'req / sec', - label: 'Status Code', - metric_id: 1, - prometheus_endpoint_path: - '/root/rails_nodb/environments/3/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29', - result: [ - { - metric: { status_code: '1xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 0], - ['2019-08-30T16:00:00.000Z', 2], - ['2019-08-30T17:00:00.000Z', 0], - ['2019-08-30T18:00:00.000Z', 0], - ['2019-08-30T19:00:00.000Z', 0], - ['2019-08-30T20:00:00.000Z', 3], - ], - }, - { - metric: { status_code: '2xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 1], - ['2019-08-30T16:00:00.000Z', 3], - ['2019-08-30T17:00:00.000Z', 6], - ['2019-08-30T18:00:00.000Z', 10], - ['2019-08-30T19:00:00.000Z', 8], - ['2019-08-30T20:00:00.000Z', 6], - ], - }, - { - metric: { status_code: '3xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 1], - ['2019-08-30T16:00:00.000Z', 2], - ['2019-08-30T17:00:00.000Z', 3], - ['2019-08-30T18:00:00.000Z', 3], - ['2019-08-30T19:00:00.000Z', 2], - ['2019-08-30T20:00:00.000Z', 1], - ], - }, - { - metric: { status_code: '4xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 2], - ['2019-08-30T16:00:00.000Z', 0], - ['2019-08-30T17:00:00.000Z', 0], - ['2019-08-30T18:00:00.000Z', 2], - ['2019-08-30T19:00:00.000Z', 0], - ['2019-08-30T20:00:00.000Z', 2], - ], - }, - { - metric: { status_code: '5xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 0], - ['2019-08-30T16:00:00.000Z', 1], - ['2019-08-30T17:00:00.000Z', 0], - ['2019-08-30T18:00:00.000Z', 0], - ['2019-08-30T19:00:00.000Z', 0], - ['2019-08-30T20:00:00.000Z', 2], - ], - }, - ], - }, - ], -}; +export * from '../../frontend/monitoring/mock_data'; diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js deleted file mode 100644 index a1df5389a38..00000000000 --- a/spec/javascripts/sidebar/assignees_spec.js +++ /dev/null @@ -1,248 +0,0 @@ -import Vue from 'vue'; -import Assignee from '~/sidebar/components/assignees/assignees.vue'; -import UsersMock from './mock_data'; -import UsersMockHelper from '../helpers/user_mock_data_helper'; - -describe('Assignee component', () => { - let component; - let AssigneeComponent; - - beforeEach(() => { - AssigneeComponent = Vue.extend(Assignee); - }); - - describe('No assignees/users', () => { - it('displays no assignee icon when collapsed', () => { - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users: [], - editable: false, - }, - }).$mount(); - - const collapsed = component.$el.querySelector('.sidebar-collapsed-icon'); - - expect(collapsed.childElementCount).toEqual(1); - expect(collapsed.children[0].getAttribute('aria-label')).toEqual('None'); - expect(collapsed.children[0].classList.contains('fa')).toEqual(true); - expect(collapsed.children[0].classList.contains('fa-user')).toEqual(true); - }); - - it('displays only "None" when no users are assigned and the issue is read-only', () => { - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users: [], - editable: false, - }, - }).$mount(); - const componentTextNoUsers = component.$el.querySelector('.assign-yourself').innerText.trim(); - - expect(componentTextNoUsers).toBe('None'); - expect(componentTextNoUsers.indexOf('assign yourself')).toEqual(-1); - }); - - it('displays only "None" when no users are assigned and the issue can be edited', () => { - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users: [], - editable: true, - }, - }).$mount(); - const componentTextNoUsers = component.$el.querySelector('.assign-yourself').innerText.trim(); - - expect(componentTextNoUsers.indexOf('None')).toEqual(0); - expect(componentTextNoUsers.indexOf('assign yourself')).toBeGreaterThan(0); - }); - - it('emits the assign-self event when "assign yourself" is clicked', () => { - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users: [], - editable: true, - }, - }).$mount(); - - spyOn(component, '$emit'); - component.$el.querySelector('.assign-yourself .btn-link').click(); - - expect(component.$emit).toHaveBeenCalledWith('assign-self'); - }); - }); - - describe('One assignee/user', () => { - it('displays one assignee icon when collapsed', () => { - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users: [UsersMock.user], - editable: false, - }, - }).$mount(); - - const collapsed = component.$el.querySelector('.sidebar-collapsed-icon'); - const assignee = collapsed.children[0]; - - expect(collapsed.childElementCount).toEqual(1); - expect(assignee.querySelector('.avatar').getAttribute('src')).toEqual(UsersMock.user.avatar); - expect(assignee.querySelector('.avatar').getAttribute('alt')).toEqual( - `${UsersMock.user.name}'s avatar`, - ); - - expect(assignee.querySelector('.author').innerText.trim()).toEqual(UsersMock.user.name); - }); - }); - - describe('Two or more assignees/users', () => { - it('has no "cannot merge" tooltip when every user can merge', () => { - const users = UsersMockHelper.createNumberRandomUsers(2); - users[0].can_merge = true; - users[1].can_merge = true; - - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000/', - users, - editable: true, - issuableType: 'merge_request', - }, - }).$mount(); - - expect(component.collapsedTooltipTitle).not.toContain('cannot merge'); - }); - - it('displays two assignee icons when collapsed', () => { - const users = UsersMockHelper.createNumberRandomUsers(2); - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users, - editable: false, - }, - }).$mount(); - - const collapsed = component.$el.querySelector('.sidebar-collapsed-icon'); - - expect(collapsed.childElementCount).toEqual(2); - - const first = collapsed.children[0]; - - expect(first.querySelector('.avatar').getAttribute('src')).toEqual(users[0].avatar); - expect(first.querySelector('.avatar').getAttribute('alt')).toEqual( - `${users[0].name}'s avatar`, - ); - - expect(first.querySelector('.author').innerText.trim()).toEqual(users[0].name); - - const second = collapsed.children[1]; - - expect(second.querySelector('.avatar').getAttribute('src')).toEqual(users[1].avatar); - expect(second.querySelector('.avatar').getAttribute('alt')).toEqual( - `${users[1].name}'s avatar`, - ); - - expect(second.querySelector('.author').innerText.trim()).toEqual(users[1].name); - }); - - it('displays one assignee icon and counter when collapsed', () => { - const users = UsersMockHelper.createNumberRandomUsers(3); - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users, - editable: false, - }, - }).$mount(); - - const collapsed = component.$el.querySelector('.sidebar-collapsed-icon'); - - expect(collapsed.childElementCount).toEqual(2); - - const first = collapsed.children[0]; - - expect(first.querySelector('.avatar').getAttribute('src')).toEqual(users[0].avatar); - expect(first.querySelector('.avatar').getAttribute('alt')).toEqual( - `${users[0].name}'s avatar`, - ); - - expect(first.querySelector('.author').innerText.trim()).toEqual(users[0].name); - - const second = collapsed.children[1]; - - expect(second.querySelector('.avatar-counter').innerText.trim()).toEqual('+2'); - }); - - it('Shows two assignees', () => { - const users = UsersMockHelper.createNumberRandomUsers(2); - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users, - editable: true, - }, - }).$mount(); - - expect(component.$el.querySelectorAll('.user-item').length).toEqual(users.length); - expect(component.$el.querySelector('.user-list-more')).toBe(null); - }); - - it('shows sorted assignee where "can merge" users are sorted first', () => { - const users = UsersMockHelper.createNumberRandomUsers(3); - users[0].can_merge = false; - users[1].can_merge = false; - users[2].can_merge = true; - - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users, - editable: true, - }, - }).$mount(); - - expect(component.sortedAssigness[0].can_merge).toBe(true); - }); - - it('passes the sorted assignees to the uncollapsed-assignee-list', () => { - const users = UsersMockHelper.createNumberRandomUsers(3); - users[0].can_merge = false; - users[1].can_merge = false; - users[2].can_merge = true; - - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users, - editable: false, - }, - }).$mount(); - - const userItems = component.$el.querySelectorAll('.user-list .user-item a'); - - expect(userItems.length).toBe(3); - expect(userItems[0].dataset.originalTitle).toBe(users[2].name); - }); - - it('passes the sorted assignees to the collapsed-assignee-list', () => { - const users = UsersMockHelper.createNumberRandomUsers(3); - users[0].can_merge = false; - users[1].can_merge = false; - users[2].can_merge = true; - - component = new AssigneeComponent({ - propsData: { - rootPath: 'http://localhost:3000', - users, - editable: false, - }, - }).$mount(); - - const collapsedButton = component.$el.querySelector('.sidebar-collapsed-user button'); - - expect(collapsedButton.innerText.trim()).toBe(users[2].name); - }); - }); -}); diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index 3ee97b978fd..c869ff96933 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -1,213 +1,7 @@ -const RESPONSE_MAP = { - GET: { - '/gitlab-org/gitlab-shell/issues/5.json': { - id: 45, - iid: 5, - author_id: 23, - description: 'Nulla ullam commodi delectus adipisci quis sit.', - lock_version: null, - milestone_id: 21, - position: 0, - state: 'closed', - title: 'Vel et nulla voluptatibus corporis dolor iste saepe laborum.', - updated_by_id: 1, - created_at: '2017-02-02T21: 49: 49.664Z', - updated_at: '2017-05-03T22: 26: 03.760Z', - time_estimate: 0, - total_time_spent: 0, - human_time_estimate: null, - human_total_time_spent: null, - branch_name: null, - confidential: false, - assignees: [ - { - name: 'User 0', - username: 'user0', - id: 22, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', - web_url: 'http: //localhost:3001/user0', - }, - { - name: 'Marguerite Bartell', - username: 'tajuana', - id: 18, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', - web_url: 'http: //localhost:3001/tajuana', - }, - { - name: 'Laureen Ritchie', - username: 'michaele.will', - id: 16, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', - web_url: 'http: //localhost:3001/michaele.will', - }, - ], - due_date: null, - moved_to_id: null, - project_id: 4, - weight: null, - milestone: { - id: 21, - iid: 1, - project_id: 4, - title: 'v0.0', - description: 'Molestiae commodi laboriosam odio sunt eaque reprehenderit.', - state: 'active', - created_at: '2017-02-02T21: 49: 30.530Z', - updated_at: '2017-02-02T21: 49: 30.530Z', - due_date: null, - start_date: null, - }, - labels: [], - }, - '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras': { - assignees: [ - { - name: 'User 0', - username: 'user0', - id: 22, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', - web_url: 'http://localhost:3001/user0', - }, - { - name: 'Marguerite Bartell', - username: 'tajuana', - id: 18, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', - web_url: 'http://localhost:3001/tajuana', - }, - { - name: 'Laureen Ritchie', - username: 'michaele.will', - id: 16, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', - web_url: 'http://localhost:3001/michaele.will', - }, - ], - human_time_estimate: null, - human_total_time_spent: null, - participants: [ - { - name: 'User 0', - username: 'user0', - id: 22, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon', - web_url: 'http://localhost:3001/user0', - }, - { - name: 'Marguerite Bartell', - username: 'tajuana', - id: 18, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon', - web_url: 'http://localhost:3001/tajuana', - }, - { - name: 'Laureen Ritchie', - username: 'michaele.will', - id: 16, - state: 'active', - avatar_url: - 'https://www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon', - web_url: 'http://localhost:3001/michaele.will', - }, - ], - subscribed: true, - time_estimate: 0, - total_time_spent: 0, - }, - '/autocomplete/projects?project_id=15': [ - { - id: 0, - name_with_namespace: 'No project', - }, - { - id: 20, - name_with_namespace: '<img src=x onerror=alert(document.domain)> foo / bar', - }, - ], - }, - PUT: { - '/gitlab-org/gitlab-shell/issues/5.json': { - data: {}, - }, - }, - POST: { - '/gitlab-org/gitlab-shell/issues/5/move': { - id: 123, - iid: 5, - author_id: 1, - description: 'some description', - lock_version: 5, - milestone_id: null, - state: 'opened', - title: 'some title', - updated_by_id: 1, - created_at: '2017-06-27T19:54:42.437Z', - updated_at: '2017-08-18T03:39:49.222Z', - time_estimate: 0, - total_time_spent: 0, - human_time_estimate: null, - human_total_time_spent: null, - branch_name: null, - confidential: false, - assignees: [], - due_date: null, - moved_to_id: null, - project_id: 7, - milestone: null, - labels: [], - web_url: '/root/some-project/issues/5', - }, - '/gitlab-org/gitlab-shell/issues/5/toggle_subscription': {}, - }, -}; +// No new code should be added to this file. Instead, modify the +// file this one re-exports from. For more detail about why, see: +// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349 -const mockData = { - responseMap: RESPONSE_MAP, - mediator: { - endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras', - toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', - moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', - projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', - editable: true, - currentUser: { - id: 1, - name: 'Administrator', - username: 'root', - avatar_url: - 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - }, - rootPath: '/', - fullPath: '/gitlab-org/gitlab-shell', - }, - time: { - time_estimate: 3600, - total_time_spent: 0, - human_time_estimate: '1h', - human_total_time_spent: null, - }, - user: { - avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - id: 1, - name: 'Administrator', - username: 'root', - }, -}; +import mockData from '../../../spec/frontend/sidebar/mock_data'; export default mockData; diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index c7a5ac783b3..caa84faa9c1 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -38,13 +38,29 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do .and_return(double(execute: kubernetes_namespace)) end - it { is_expected.to be_falsey } - - context 'and the service_account_token is blank' do - let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) } + context 'and the knative version role binding is missing' do + before do + allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new) + .and_return(double(execute: nil)) + end it { is_expected.to be_truthy } end + + context 'and the knative version role binding already exists' do + before do + allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new) + .and_return(double(execute: true)) + end + + it { is_expected.to be_falsey } + + context 'and the service_account_token is blank' do + let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) } + + it { is_expected.to be_truthy } + end + end end end @@ -115,6 +131,24 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do subject end end + + context 'knative version role binding is missing' do + before do + allow(Clusters::KubernetesNamespaceFinder).to receive(:new) + .and_return(double(execute: kubernetes_namespace)) + allow(Clusters::KnativeVersionRoleBindingFinder).to receive(:new) + .and_return(double(execute: nil)) + end + + it 'creates the knative version role binding' do + expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService) + .to receive(:new) + .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace) + .and_return(service) + + subject + end + end end context 'completion is not required' do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 6c9467916de..0ab3e513e24 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -17,13 +17,13 @@ describe Gitlab::Git::Commit, :seed_helper do @committer = { email: 'mike@smith.com', name: "Mike Smith", - time: Time.now + time: Time.new(2000, 1, 1, 0, 0, 0, "+08:00") } @author = { email: 'john@smith.com', name: "John Smith", - time: Time.now + time: Time.new(2000, 1, 1, 0, 0, 0, "-08:00") } @parents = [rugged_repo.head.target] @@ -48,7 +48,7 @@ describe Gitlab::Git::Commit, :seed_helper do it { expect(@commit.id).to eq(@raw_commit.oid) } it { expect(@commit.sha).to eq(@raw_commit.oid) } it { expect(@commit.safe_message).to eq(@raw_commit.message) } - it { expect(@commit.created_at).to eq(@raw_commit.author[:time]) } + it { expect(@commit.created_at).to eq(@raw_commit.committer[:time]) } it { expect(@commit.date).to eq(@raw_commit.committer[:time]) } it { expect(@commit.author_email).to eq(@author[:email]) } it { expect(@commit.author_name).to eq(@author[:name]) } @@ -79,13 +79,27 @@ describe Gitlab::Git::Commit, :seed_helper do it { expect(commit.id).to eq(id) } it { expect(commit.sha).to eq(id) } it { expect(commit.safe_message).to eq(body) } - it { expect(commit.created_at).to eq(Time.at(committer.date.seconds)) } + it { expect(commit.created_at).to eq(Time.at(committer.date.seconds).utc) } it { expect(commit.author_email).to eq(author.email) } it { expect(commit.author_name).to eq(author.name) } it { expect(commit.committer_name).to eq(committer.name) } it { expect(commit.committer_email).to eq(committer.email) } it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) } + context 'non-UTC dates' do + let(:seconds) { Time.now.to_i } + + it 'sets timezones correctly' do + gitaly_commit.author.date.seconds = seconds + gitaly_commit.author.timezone = '-0800' + gitaly_commit.committer.date.seconds = seconds + gitaly_commit.committer.timezone = '+0800' + + expect(commit.authored_date).to eq(Time.at(seconds, in: '-08:00')) + expect(commit.committed_date).to eq(Time.at(seconds, in: '+08:00')) + end + end + context 'body_size != body.size' do let(:body) { (+"").force_encoding('ASCII-8BIT') } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 916f5536ebd..95435c6dabd 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1197,6 +1197,54 @@ describe Ci::Build do end end + describe '#expanded_kubernetes_namespace' do + let(:build) { create(:ci_build, environment: environment, options: options) } + + subject { build.expanded_kubernetes_namespace } + + context 'environment and namespace are not set' do + let(:environment) { nil } + let(:options) { nil } + + it { is_expected.to be_nil } + end + + context 'environment is specified' do + let(:environment) { 'production' } + + context 'namespace is not set' do + let(:options) { nil } + + it { is_expected.to be_nil } + end + + context 'namespace is provided' do + let(:options) do + { + environment: { + name: environment, + kubernetes: { + namespace: namespace + } + } + } + end + + context 'with a static value' do + let(:namespace) { 'production' } + + it { is_expected.to eq namespace } + end + + context 'with a dynamic value' do + let(:namespace) { 'deploy-$CI_COMMIT_REF_NAME'} + + it { is_expected.to eq 'deploy-master' } + end + end + end + end + describe '#starts_environment?' do subject { build.starts_environment? } @@ -2987,6 +3035,32 @@ describe Ci::Build do end end + describe '#deployment_variables' do + let(:build) { create(:ci_build, environment: environment) } + let(:environment) { 'production' } + let(:kubernetes_namespace) { 'namespace' } + let(:project_variables) { double } + + subject { build.deployment_variables(environment: environment) } + + before do + allow(build).to receive(:expanded_kubernetes_namespace) + .and_return(kubernetes_namespace) + + allow(build.project).to receive(:deployment_variables) + .with(environment: environment, kubernetes_namespace: kubernetes_namespace) + .and_return(project_variables) + end + + it { is_expected.to eq(project_variables) } + + context 'environment is nil' do + let(:environment) { nil } + + it { is_expected.to be_empty } + end + end + describe '#scoped_variables_hash' do context 'when overriding CI variables' do before do diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index d53fc32cfef..4271cf9f1b3 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -290,6 +290,26 @@ describe Clusters::Platforms::Kubernetes do it { is_expected.to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) } it { is_expected.to include(key: 'KUBE_NAMESPACE', value: namespace) } it { is_expected.to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) } + + context 'custom namespace is provided' do + let(:custom_namespace) { 'custom-namespace' } + + subject do + platform.predefined_variables( + project: project, + environment_name: environment_name, + kubernetes_namespace: custom_namespace + ) + end + + before do + allow(platform).to receive(:kubeconfig).with(custom_namespace).and_return(kubeconfig) + end + + it { is_expected.to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) } + it { is_expected.to include(key: 'KUBE_NAMESPACE', value: custom_namespace) } + it { is_expected.to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) } + end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 839c4cadb5e..37c8484d69b 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -359,7 +359,7 @@ eos it { expect(data).to be_a(Hash) } it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') } - it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46Z') } + it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46+00:00') } it { expect(data[:added]).to contain_exactly("bar/branch-test.txt") } it { expect(data[:modified]).to eq([]) } it { expect(data[:removed]).to eq([]) } diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 45cd2768708..d84a8665dc8 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -106,6 +106,40 @@ describe Milestone do end end + describe '#merge_requests_enabled?' do + context "per project" do + it "is true for projects with MRs enabled" do + project = create(:project, :merge_requests_enabled) + milestone = create(:milestone, project: project) + + expect(milestone.merge_requests_enabled?).to be(true) + end + + it "is false for projects with MRs disabled" do + project = create(:project, :repository_enabled, :merge_requests_disabled) + milestone = create(:milestone, project: project) + + expect(milestone.merge_requests_enabled?).to be(false) + end + + it "is false for projects with repository disabled" do + project = create(:project, :repository_disabled) + milestone = create(:milestone, project: project) + + expect(milestone.merge_requests_enabled?).to be(false) + end + end + + context "per group" do + let(:group) { create(:group) } + let(:milestone) { create(:milestone, group: group) } + + it "is always true for groups, for performance reasons" do + expect(milestone.merge_requests_enabled?).to be(true) + end + end + end + describe "unique milestone title" do context "per project" do it "does not accept the same title in a project twice" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index b1f88c4530e..37604a557ea 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2765,8 +2765,9 @@ describe Project do describe '#deployment_variables' do let(:project) { create(:project) } let(:environment) { 'production' } + let(:namespace) { 'namespace' } - subject { project.deployment_variables(environment: environment) } + subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) } before do expect(project).to receive(:deployment_platform).with(environment: environment) @@ -2785,7 +2786,7 @@ describe Project do before do expect(deployment_platform).to receive(:predefined_variables) - .with(project: project, environment_name: environment) + .with(project: project, environment_name: environment, kubernetes_namespace: namespace) .and_return(platform_variables) end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 675b06b057c..eda2f6d854f 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -131,7 +131,7 @@ describe API::Branches do end new_branch_name = 'protected-branch' - CreateBranchService.new(project, current_user).execute(new_branch_name, 'master') + ::Branches::CreateService.new(project, current_user).execute(new_branch_name, 'master') create(:protected_branch, name: new_branch_name, project: project) expect do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index ec18156f49f..378441220c7 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -315,11 +315,11 @@ describe API::Files do expect(range['commit']['message']) .to eq("Files, encoding and much more\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n") - expect(range['commit']['authored_date']).to eq('2014-02-27T08:14:56.000Z') + expect(range['commit']['authored_date']).to eq('2014-02-27T10:14:56.000+02:00') expect(range['commit']['author_name']).to eq('Dmitriy Zaporozhets') expect(range['commit']['author_email']).to eq('dmitriy.zaporozhets@gmail.com') - expect(range['commit']['committed_date']).to eq('2014-02-27T08:14:56.000Z') + expect(range['commit']['committed_date']).to eq('2014-02-27T10:14:56.000+02:00') expect(range['commit']['committer_name']).to eq('Dmitriy Zaporozhets') expect(range['commit']['committer_email']).to eq('dmitriy.zaporozhets@gmail.com') end diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb new file mode 100644 index 00000000000..4d0bb59b030 --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Setting an issue as confidential' do + include GraphqlHelpers + + let(:current_user) { create(:user) } + let(:issue) { create(:issue) } + let(:project) { issue.project } + let(:input) { { confidential: true } } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: issue.iid.to_s + } + graphql_mutation(:issue_set_confidential, variables.merge(input), + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + confidential + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_set_confidential) + end + + before do + project.add_developer(current_user) + end + + it 'returns an error if the user is not allowed to update the issue' do + error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + + it 'updates the issue confidentiality' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['issue']['confidential']).to be_truthy + end +end diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb new file mode 100644 index 00000000000..9451674161c --- /dev/null +++ b/spec/requests/user_avatar_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Loading a user avatar' do + let(:user) { create(:user, :with_avatar) } + + context 'when logged in' do + # The exact query count will vary depending on the 2FA settings of the + # instance, group, and user. Removing those extra 2FA queries in this case + # may not be a good idea, so we just set up the ideal case. + before do + stub_application_setting(require_two_factor_authentication: true) + + login_as(create(:user, :two_factor)) + end + + # One each for: current user, avatar user, and upload record + it 'only performs three SQL queries' do + get user.avatar_url # Skip queries on first application load + + expect(response).to have_gitlab_http_status(200) + expect { get user.avatar_url }.not_to exceed_query_limit(3) + end + end + + context 'when logged out' do + # One each for avatar user and upload record + it 'only performs two SQL queries' do + get user.avatar_url # Skip queries on first application load + + expect(response).to have_gitlab_http_status(200) + expect { get user.avatar_url }.not_to exceed_query_limit(2) + end + end +end diff --git a/spec/routing/instance_statistics_routing_spec.rb b/spec/routing/instance_statistics_routing_spec.rb index b94faabfa1d..48a3ac4695c 100644 --- a/spec/routing/instance_statistics_routing_spec.rb +++ b/spec/routing/instance_statistics_routing_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe 'Instance Statistics', 'routing' do include RSpec::Rails::RequestExampleGroup - it "routes '/-/instance_statistics' to conversational development index" do - expect(get('/-/instance_statistics')).to redirect_to('/-/instance_statistics/conversational_development_index') + it "routes '/-/instance_statistics' to dev ops score" do + expect(get('/-/instance_statistics')).to redirect_to('/-/instance_statistics/dev_ops_score') end end diff --git a/spec/services/create_branch_service_spec.rb b/spec/services/branches/create_service_spec.rb index 9661173c9e7..444491ed6f3 100644 --- a/spec/services/create_branch_service_spec.rb +++ b/spec/services/branches/create_service_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -describe CreateBranchService do +describe Branches::CreateService do let(:user) { create(:user) } - let(:service) { described_class.new(project, user) } + subject(:service) { described_class.new(project, user) } describe '#execute' do context 'when repository is empty' do @@ -30,7 +30,7 @@ describe CreateBranchService do allow(project.repository).to receive(:add_branch).and_return(false) end - it 'retruns an error with the branch name' do + it 'returns an error with the branch name' do result = service.execute('my-feature', 'master') expect(result[:status]).to eq(:error) diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/branches/delete_merged_service_spec.rb index dffc2bd93ee..962af8110f7 100644 --- a/spec/services/delete_merged_branches_service_spec.rb +++ b/spec/services/branches/delete_merged_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe DeleteMergedBranchesService do +describe Branches::DeleteMergedService do include ProjectForksHelper subject(:service) { described_class.new(project, project.owner) } diff --git a/spec/services/delete_branch_service_spec.rb b/spec/services/branches/delete_service_spec.rb index b8064c2cbc1..b4848978a6f 100644 --- a/spec/services/delete_branch_service_spec.rb +++ b/spec/services/branches/delete_service_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' -describe DeleteBranchService do +describe Branches::DeleteService do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } - let(:service) { described_class.new(project, user) } + subject(:service) { described_class.new(project, user) } shared_examples 'a deleted branch' do |branch_name| it 'removes the branch' do diff --git a/spec/services/branches/validate_new_service_spec.rb b/spec/services/branches/validate_new_service_spec.rb new file mode 100644 index 00000000000..460f28b5844 --- /dev/null +++ b/spec/services/branches/validate_new_service_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Branches::ValidateNewService do + let(:project) { create(:project, :repository) } + subject(:service) { described_class.new(project) } + + describe '#execute' do + context 'validation' do + it 'returns error with an invalid branch name' do + result = service.execute('refs/heads/invalid_branch') + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Branch name is invalid') + end + + it 'returns success with a valid branch name' do + result = service.execute('valid_branch_name') + + expect(result[:status]).to eq(:success) + end + end + + context 'branch exist' do + it 'returns error when branch exists' do + result = service.execute('master') + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Branch already exists') + end + + it 'returns success when branch name is available' do + result = service.execute('valid_branch_name') + + expect(result[:status]).to eq(:success) + end + end + end +end diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb index 291e63bbe4a..a9e3e881fd4 100644 --- a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb +++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb @@ -22,7 +22,7 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do before do stub_kubeclient_discover(api_url) - stub_kubeclient_get_namespace(api_url) + stub_kubeclient_get_namespaces(api_url) stub_kubeclient_get_service_account_error(api_url, 'gitlab') stub_kubeclient_create_service_account(api_url) stub_kubeclient_get_secret_error(api_url, 'gitlab-token') @@ -39,6 +39,8 @@ describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) + stub_kubeclient_put_cluster_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME) + stub_kubeclient_put_cluster_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME) stub_kubeclient_get_secret( api_url, diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb index 4df73fcc2ae..b40861e5aaf 100644 --- a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb +++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb @@ -141,12 +141,15 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do before do cluster.platform_kubernetes.rbac! + stub_kubeclient_get_namespaces(api_url) stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace) stub_kubeclient_create_role_binding(api_url, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) + stub_kubeclient_put_cluster_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME) + stub_kubeclient_put_cluster_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME) end it_behaves_like 'creates service account and token' @@ -234,6 +237,30 @@ describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do ) ) end + + it 'creates a role and role binding granting the ability to get the version of deployments in knative-serving namespace' do + subject + + expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME}").with( + body: hash_including( + metadata: { + name: Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_BINDING_NAME + }, + roleRef: { + apiGroup: "rbac.authorization.k8s.io", + kind: "ClusterRole", + name: Clusters::Kubernetes::GITLAB_KNATIVE_VERSION_ROLE_NAME + }, + subjects: [ + { + kind: "ServiceAccount", + name: service_account_name, + namespace: namespace + } + ] + ) + ) + end end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 1bdea973685..61c8103353c 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -211,7 +211,8 @@ describe MergeRequests::MergeService do end it 'does not delete the source branch' do - expect(DeleteBranchService).not_to receive(:new) + expect(::Branches::DeleteService).not_to receive(:new) + service.execute(merge_request) end end @@ -226,7 +227,7 @@ describe MergeRequests::MergeService do end it 'does not delete the source branch' do - expect(DeleteBranchService).not_to receive(:new) + expect(::Branches::DeleteService).not_to receive(:new) service.execute(merge_request) end end @@ -238,7 +239,7 @@ describe MergeRequests::MergeService do end it 'removes the source branch using the author user' do - expect(DeleteBranchService).to receive(:new) + expect(::Branches::DeleteService).to receive(:new) .with(merge_request.source_project, merge_request.author) .and_call_original service.execute(merge_request) @@ -248,7 +249,7 @@ describe MergeRequests::MergeService do let(:service) { described_class.new(project, user, merge_params.merge('should_remove_source_branch' => false)) } it 'does not delete the source branch' do - expect(DeleteBranchService).not_to receive(:new) + expect(::Branches::DeleteService).not_to receive(:new) service.execute(merge_request) end end @@ -260,7 +261,7 @@ describe MergeRequests::MergeService do end it 'removes the source branch using the current user' do - expect(DeleteBranchService).to receive(:new) + expect(::Branches::DeleteService).to receive(:new) .with(merge_request.source_project, user) .and_call_original service.execute(merge_request) diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index cccafddc450..77e38f1eb4c 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -61,7 +61,7 @@ describe MergeRequests::MergeToRefService do end it 'does not delete the source branch' do - expect(DeleteBranchService).not_to receive(:new) + expect(::Branches::DeleteService).not_to receive(:new) process_merge_to_ref end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 9d0ad60a624..9e69f179da9 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -113,7 +113,7 @@ describe MergeRequests::RefreshService do context 'when source branch ref does not exists' do before do - DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch) + ::Branches::DeleteService.new(@project, @user).execute(@merge_request.source_branch) end it 'closes MRs without source branch ref' do diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 818794ed956..0d7e17ad52c 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -1015,6 +1015,30 @@ describe TodoService do end end + describe '#mark_todo_as_done' do + it 'marks a todo done' do + todo1 = create(:todo, :pending, user: john_doe) + + described_class.new.mark_todo_as_done(todo1, john_doe) + + expect(todo1.reload.state).to eq('done') + end + + context 'when todo is already in state done' do + let(:todo1) { create(:todo, :done, user: john_doe) } + + it 'does not update the todo' do + expect { described_class.new.mark_todo_as_done(todo1, john_doe) }.not_to change(todo1.reload, :state) + end + + it 'does not update cache count' do + expect(john_doe).not_to receive(:update_todos_count_cache) + + described_class.new.mark_todo_as_done(todo1, john_doe) + end + end + end + describe '#mark_all_todos_as_done_by_user' do it 'marks all todos done' do todo1 = create(:todo, user: john_doe, state: :pending) diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index 677aef57661..cac43e94a92 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -194,6 +194,11 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_put_cluster_role_binding(api_url, name) + WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/#{name}") + .to_return(kube_response({})) + end + def stub_kubeclient_get_role_binding(api_url, name, namespace: 'default') WebMock.stub_request(:get, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{name}") .to_return(kube_response({})) @@ -219,11 +224,21 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_get_namespaces(api_url) + WebMock.stub_request(:get, api_url + '/api/v1/namespaces') + .to_return(kube_response(kube_v1_namespace_list_body)) + end + def stub_kubeclient_get_namespace(api_url, namespace: 'default') WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}") .to_return(kube_response({})) end + def stub_kubeclient_put_cluster_role(api_url, name) + WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterroles/#{name}") + .to_return(kube_response({})) + end + def stub_kubeclient_put_role(api_url, name, namespace: 'default') WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}") .to_return(kube_response({})) @@ -257,6 +272,20 @@ module KubernetesHelpers } end + def kube_v1_namespace_list_body + { + "kind" => "NamespaceList", + "apiVersion" => "v1", + "items" => [ + { + "metadata" => { + "name" => "knative-serving" + } + } + ] + } + end + def kube_v1beta1_discovery_body { "kind" => "APIResourceList", diff --git a/spec/support/helpers/position_tracer_helpers.rb b/spec/support/helpers/position_tracer_helpers.rb index bbf6e06dd40..7516694d4fe 100644 --- a/spec/support/helpers/position_tracer_helpers.rb +++ b/spec/support/helpers/position_tracer_helpers.rb @@ -50,7 +50,7 @@ module PositionTracerHelpers end def create_branch(new_name, branch_name) - CreateBranchService.new(project, current_user).execute(new_name, branch_name) + ::Branches::CreateService.new(project, current_user).execute(new_name, branch_name) end def create_file(branch_name, file_name, content) diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index c24418b2f90..8962d98218a 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -74,7 +74,7 @@ shared_examples 'handle uploads' do end before do - expect(FileUploader).to receive(:generate_secret).and_return(secret) + allow(FileUploader).to receive(:generate_secret).and_return(secret) UploadService.new(model, jpg, uploader_class).execute end @@ -88,6 +88,18 @@ shared_examples 'handle uploads' do end end + context 'when the upload does not have a MIME type that Rails knows' do + let(:po) { fixture_file_upload('spec/fixtures/missing_metadata.po', 'text/plain') } + + it 'falls back to the null type' do + UploadService.new(model, po, uploader_class).execute + + get :show, params: params.merge(secret: secret, filename: 'missing_metadata.po') + + expect(response.headers['Content-Type']).to eq('application/octet-stream') + end + end + context "when the model is public" do before do model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb index c0db4cdde72..da966fd2200 100644 --- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb @@ -8,11 +8,14 @@ RSpec.shared_examples 'a creatable merge request' do page.within '.dropdown-menu-user' do click_link user2.name end + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user2.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user2.name end + click_link 'Assign to me' + expect(find('input[name="merge_request[assignee_ids][]"]', visible: false).value).to match(user.id.to_s) page.within '.js-assignee-search' do expect(page).to have_content user.name @@ -22,6 +25,7 @@ RSpec.shared_examples 'a creatable merge request' do page.within '.issue-milestone' do click_link milestone.title end + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) page.within '.js-milestone-select' do expect(page).to have_content milestone.title @@ -32,6 +36,7 @@ RSpec.shared_examples 'a creatable merge request' do click_link label.title click_link label2.title end + page.within '.js-label-select' do expect(page).to have_content label.title end @@ -58,8 +63,9 @@ RSpec.shared_examples 'a creatable merge request' do it 'updates the branches when selecting a new target project', :js do target_project_member = target_project.owner - CreateBranchService.new(target_project, target_project_member) - .execute('a-brand-new-branch-to-test', 'master') + ::Branches::CreateService.new(target_project, target_project_member) + .execute('a-brand-new-branch-to-test', 'master') + visit project_new_merge_request_path(source_project) first('.js-target-project').click diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb index a218ca921d9..8c983859e36 100644 --- a/spec/workers/delete_merged_branches_worker_spec.rb +++ b/spec/workers/delete_merged_branches_worker_spec.rb @@ -8,8 +8,8 @@ describe DeleteMergedBranchesWorker do let(:project) { create(:project, :repository) } describe "#perform" do - it "calls DeleteMergedBranchesService" do - expect_any_instance_of(DeleteMergedBranchesService).to receive(:execute).and_return(true) + it "delegates to Branches::DeleteMergedService" do + expect_any_instance_of(::Branches::DeleteMergedService).to receive(:execute).and_return(true) worker.perform(project.id, project.owner.id) end |