diff options
Diffstat (limited to 'spec/controllers/projects')
43 files changed, 1657 insertions, 392 deletions
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb index 5ec8d8d41d7..4ae29ba7f54 100644 --- a/spec/controllers/projects/badges_controller_spec.rb +++ b/spec/controllers/projects/badges_controller_spec.rb @@ -7,51 +7,115 @@ describe Projects::BadgesController do let!(:pipeline) { create(:ci_empty_pipeline) } let(:user) { create(:user) } - before do - project.add_maintainer(user) - sign_in(user) - end + shared_examples 'a badge resource' do |badge_type| + context 'when pipelines are public' do + before do + project.update!(public_builds: true) + end + + context 'when project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it "returns the #{badge_type} badge to unauthenticated users" do + get_badge(badge_type) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when project is restricted' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + project.add_guest(user) + sign_in(user) + end + + it "returns the #{badge_type} badge to guest users" do + get_badge(badge_type) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end - it 'requests the pipeline badge successfully' do - get_badge(:pipeline) + context 'format' do + before do + project.add_maintainer(user) + sign_in(user) + end - expect(response).to have_gitlab_http_status(:ok) - end + it 'renders the `flat` badge layout by default' do + get_badge(badge_type) - it 'requests the coverage badge successfully' do - get_badge(:coverage) + expect(response).to render_template('projects/badges/badge') + end - expect(response).to have_gitlab_http_status(:ok) - end + context 'when style param is set to `flat`' do + it 'renders the `flat` badge layout' do + get_badge(badge_type, 'flat') - it 'renders the `flat` badge layout by default' do - get_badge(:coverage) + expect(response).to render_template('projects/badges/badge') + end + end - expect(response).to render_template('projects/badges/badge') - end + context 'when style param is set to an invalid type' do + it 'renders the `flat` (default) badge layout' do + get_badge(badge_type, 'xxx') + + expect(response).to render_template('projects/badges/badge') + end + end - context 'when style param is set to `flat`' do - it 'renders the `flat` badge layout' do - get_badge(:coverage, 'flat') + context 'when style param is set to `flat-square`' do + it 'renders the `flat-square` badge layout' do + get_badge(badge_type, 'flat-square') - expect(response).to render_template('projects/badges/badge') + expect(response).to render_template('projects/badges/badge_flat-square') + end + end end - end - context 'when style param is set to an invalid type' do - it 'renders the `flat` (default) badge layout' do - get_badge(:coverage, 'xxx') + context 'when pipelines are not public' do + before do + project.update!(public_builds: false) + end - expect(response).to render_template('projects/badges/badge') + context 'when project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it 'returns 404 to unauthenticated users' do + get_badge(badge_type) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when project is restricted to the user' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + project.add_guest(user) + sign_in(user) + end + + it 'defaults to project permissions' do + get_badge(:coverage) + + expect(response).to have_gitlab_http_status(:not_found) + end + end end end - context 'when style param is set to `flat-square`' do - it 'renders the `flat-square` badge layout' do - get_badge(:coverage, 'flat-square') + describe '#pipeline' do + it_behaves_like 'a badge resource', :pipeline + end - expect(response).to render_template('projects/badges/badge_flat-square') - end + describe '#coverage' do + it_behaves_like 'a badge resource', :coverage end def get_badge(badge, style = nil) diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 44500d3cde3..45aebd1554c 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -160,7 +160,7 @@ describe Projects::BlobController do it 'renders diff context lines Gitlab::Diff::Line array' do do_get(since: 1, to: 2, offset: 0, from_merge_request: true) - lines = JSON.parse(response.body) + lines = json_response expect(lines.size).to eq(diff_lines.size) lines.each do |line| @@ -173,7 +173,7 @@ describe Projects::BlobController do it 'handles full being true' do do_get(full: true, from_merge_request: true) - lines = JSON.parse(response.body) + lines = json_response expect(lines.size).to eq(diff_lines.size) end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index ae85000b4e0..543479d8dd5 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -65,14 +65,12 @@ describe Projects::BoardsController do it 'returns a list of project boards' do create_list(:board, 2, project: project) - expect(Boards::Visits::LatestService).not_to receive(:new) + expect(Boards::VisitsFinder).not_to receive(:new) list_boards format: :json - parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('boards') - expect(parsed_response.length).to eq 2 + expect(json_response.length).to eq 2 end context 'with unauthorized user' do diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index c778b7888dc..f5bcea4a097 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -92,7 +92,7 @@ describe Projects::BranchesController do end it 'posts a system note' do - expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch") + expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch", branch_project: project) post :create, params: { @@ -103,6 +103,75 @@ describe Projects::BranchesController do } end + context 'confidential_issue_project_id is present' do + let(:confidential_issue_project) { create(:project) } + + def create_branch_with_confidential_issue_project + post( + :create, + params: { + namespace_id: project.namespace, + project_id: project, + branch_name: branch, + confidential_issue_project_id: confidential_issue_project.id, + issue_iid: issue.iid + } + ) + end + + context 'create_confidential_merge_request feature is enabled' do + before do + stub_feature_flags(create_confidential_merge_request: true) + end + + context 'user cannot update issue' do + let(:issue) { create(:issue, project: confidential_issue_project) } + + it 'does not post a system note' do + expect(SystemNoteService).not_to receive(:new_issue_branch) + + create_branch_with_confidential_issue_project + end + end + + context 'user can update issue' do + before do + confidential_issue_project.add_reporter(user) + end + + context 'issue is under the specified project' do + let(:issue) { create(:issue, project: confidential_issue_project) } + + it 'posts a system note' do + expect(SystemNoteService).to receive(:new_issue_branch).with(issue, confidential_issue_project, user, "1-feature-branch", branch_project: project) + + create_branch_with_confidential_issue_project + end + end + + context 'issue is not under the specified project' do + it 'does not post a system note' do + expect(SystemNoteService).not_to receive(:new_issue_branch) + + create_branch_with_confidential_issue_project + end + end + end + end + + context 'create_confidential_merge_request feature is disabled' do + before do + stub_feature_flags(create_confidential_merge_request: false) + end + + it 'posts a system note on project' do + expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch", branch_project: project) + + create_branch_with_confidential_issue_project + end + end + end + context 'repository-less project' do let(:project) { create :project } @@ -123,7 +192,11 @@ describe Projects::BranchesController do expect(response).to redirect_to project_tree_path(project, branch) end - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + before do + create(:cluster, :provided_by_gcp, projects: [project]) + end + it 'redirects to autodeploy setup page' do result = { status: :success, branch: double(name: branch) } @@ -143,22 +216,6 @@ describe Projects::BranchesController do end end - context 'when user configured kubernetes from Integration > Kubernetes' do - before do - project.services << build(:kubernetes_service) - end - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - before do - create(:cluster, :provided_by_gcp, projects: [project]) - end - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - it 'redirects to autodeploy setup page' do result = { status: :success, branch: double(name: branch) } @@ -438,10 +495,8 @@ describe Projects::BranchesController do search: 'master' } - parsed_response = JSON.parse(response.body) - - expect(parsed_response.length).to eq 1 - expect(parsed_response.first).to eq 'master' + expect(json_response.length).to eq 1 + expect(json_response.first).to eq 'master' end end @@ -519,4 +574,26 @@ describe Projects::BranchesController do end end end + + describe 'GET diverging_commit_counts' do + before do + sign_in(user) + + get :diverging_commit_counts, + format: :json, + params: { + namespace_id: project.namespace, + project_id: project, + names: ['fix', 'add-pdf-file', 'branch-merged'] + } + end + + it 'returns the commit counts behind and ahead of default branch' do + expect(json_response).to eq( + "fix" => { "behind" => 29, "ahead" => 2 }, + "branch-merged" => { "behind" => 1, "ahead" => 0 }, + "add-pdf-file" => { "behind" => 0, "ahead" => 3 } + ) + end + end end diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb index cc6ac83ca38..14128fb5b0e 100644 --- a/spec/controllers/projects/ci/lints_controller_spec.rb +++ b/spec/controllers/projects/ci/lints_controller_spec.rb @@ -20,9 +20,7 @@ describe Projects::Ci::LintsController do get :show, params: { namespace_id: project.namespace, project_id: project } end - it 'is success' do - expect(response).to be_success - end + it { expect(response).to be_successful } it 'renders show page' do expect(response).to render_template :show @@ -78,9 +76,7 @@ describe Projects::Ci::LintsController do post :create, params: { namespace_id: project.namespace, project_id: project, content: content } end - it 'is success' do - expect(response).to be_success - end + it { expect(response).to be_successful } it 'render show page' do expect(response).to render_template :show @@ -107,7 +103,7 @@ describe Projects::Ci::LintsController do end it 'assigns errors' do - expect(assigns[:error]).to eq('jobs:rubocop config contains unknown keys: scriptt') + expect(assigns[:error]).to eq('root config contains unknown keys: rubocop') end end diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 8d37bd82d21..35cbab57037 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -340,7 +340,6 @@ describe Projects::ClustersController do describe 'security' do before do - allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end @@ -438,7 +437,6 @@ describe Projects::ClustersController do end before do - allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end @@ -449,6 +447,7 @@ describe Projects::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, platform_kubernetes_attributes: { namespace: 'my-namespace' } @@ -464,6 +463,7 @@ describe Projects::ClustersController do expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') end @@ -475,6 +475,7 @@ describe Projects::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, platform_kubernetes_attributes: { namespace: 'my-namespace' } @@ -489,6 +490,7 @@ describe Projects::ClustersController do expect(response).to have_http_status(:no_content) expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') end end diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index b5c6382a26d..afd5cb15e0f 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -45,14 +45,14 @@ describe Projects::CommitController do it 'handles binary files' do go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html') - expect(response).to be_success + expect(response).to be_successful end shared_examples "export as" do |format| it "does generally work" do go(id: commit.id, format: format) - expect(response).to be_success + expect(response).to be_successful end it "generates it" do @@ -110,7 +110,7 @@ describe Projects::CommitController do id: commit.id }) - expect(response).to be_success + expect(response).to be_successful end end @@ -177,7 +177,7 @@ describe Projects::CommitController do id: commit.id }) - expect(response).not_to be_success + expect(response).not_to be_successful expect(response).to have_gitlab_http_status(404) end end @@ -234,7 +234,7 @@ describe Projects::CommitController do id: master_pickable_commit.id }) - expect(response).not_to be_success + expect(response).not_to be_successful expect(response).to have_gitlab_http_status(404) end end @@ -378,8 +378,8 @@ describe Projects::CommitController do get_pipelines(id: commit.id, format: :json) expect(response).to be_ok - expect(JSON.parse(response.body)['pipelines']).not_to be_empty - expect(JSON.parse(response.body)['count']['all']).to eq 1 + expect(json_response['pipelines']).not_to be_empty + expect(json_response['count']['all']).to eq 1 expect(response).to include_pagination_headers end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 9db1ac2a46c..9c4d6fdcb2a 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -79,7 +79,7 @@ describe Projects::CommitsController do end it "renders as atom" do - expect(response).to be_success + expect(response).to be_successful expect(response.content_type).to eq('application/atom+xml') end @@ -104,7 +104,7 @@ describe Projects::CommitsController do end it "renders as HTML" do - expect(response).to be_success + expect(response).to be_successful expect(response.content_type).to eq('text/html') end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 92380a2bf09..9afc46c4be9 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::CompareController do end it 'returns successfully' do - expect(response).to be_success + expect(response).to be_successful end end @@ -49,7 +49,7 @@ describe Projects::CompareController do it 'shows some diffs with ignore whitespace change option' do show_request - expect(response).to be_success + expect(response).to be_successful diff_file = assigns(:diffs).diff_files.first expect(diff_file).not_to be_nil expect(assigns(:commits).length).to be >= 1 @@ -67,7 +67,7 @@ describe Projects::CompareController do it 'sets the diffs and commits ivars' do show_request - expect(response).to be_success + expect(response).to be_successful expect(assigns(:diffs).diff_files.first).not_to be_nil expect(assigns(:commits).length).to be >= 1 end @@ -81,7 +81,7 @@ describe Projects::CompareController do it 'sets empty diff and commit ivars' do show_request - expect(response).to be_success + expect(response).to be_successful expect(assigns(:diffs)).to eq([]) expect(assigns(:commits)).to eq([]) end @@ -94,7 +94,7 @@ describe Projects::CompareController do it 'sets empty diff and commit ivars' do show_request - expect(response).to be_success + expect(response).to be_successful expect(assigns(:diffs)).to eq([]) expect(assigns(:commits)).to eq([]) end @@ -302,8 +302,7 @@ describe Projects::CompareController do signatures_request expect(response).to have_gitlab_http_status(200) - parsed_body = JSON.parse(response.body) - signatures = parsed_body['signatures'] + signatures = json_response['signatures'] expect(signatures.size).to eq(1) expect(signatures.first['commit_sha']).to eq(signature_commit.sha) @@ -332,8 +331,7 @@ describe Projects::CompareController do signatures_request expect(response).to have_gitlab_http_status(200) - parsed_body = JSON.parse(response.body) - expect(parsed_body['signatures']).to be_empty + expect(json_response['signatures']).to be_empty end end @@ -345,8 +343,7 @@ describe Projects::CompareController do signatures_request expect(response).to have_gitlab_http_status(200) - parsed_body = JSON.parse(response.body) - expect(parsed_body['signatures']).to be_empty + expect(json_response['signatures']).to be_empty end end end diff --git a/spec/controllers/projects/cycle_analytics/events_controller_spec.rb b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb new file mode 100644 index 00000000000..b828c678d0c --- /dev/null +++ b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::CycleAnalytics::EventsController do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.add_maintainer(user) + end + + describe 'cycle analytics not set up flag' do + context 'with no data' do + it 'is empty' do + get_issue + + expect(response).to be_successful + expect(JSON.parse(response.body)['events']).to be_empty + end + end + + context 'with data' do + let(:milestone) { create(:milestone, project: project, created_at: 10.days.ago) } + let(:issue) { create(:issue, project: project, created_at: 9.days.ago) } + + before do + issue.update(milestone: milestone) + end + + it 'is not empty' do + get_issue + + expect(response).to be_successful + end + + it 'contains event detais' do + get_issue + + events = JSON.parse(response.body)['events'] + + expect(events).not_to be_empty + expect(events.first).to include('title', 'author', 'iid', 'total_time', 'created_at', 'url') + expect(events.first['title']).to eq(issue.title) + end + + context 'with data older than start date' do + it 'is empty' do + get_issue(additional_params: { cycle_analytics: { start_date: 7 } }) + + expect(response).to be_successful + + expect(JSON.parse(response.body)['events']).to be_empty + end + end + end + end + + def get_issue(additional_params: {}) + params = additional_params.merge(namespace_id: project.namespace, project_id: project) + get(:issue, params: params, format: :json) + end +end diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb index 2dc97e18113..65eee7b8ead 100644 --- a/spec/controllers/projects/cycle_analytics_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb @@ -11,6 +11,20 @@ describe Projects::CycleAnalyticsController do project.add_maintainer(user) end + context "counting page views for 'show'" do + it 'increases the counter' do + expect(Gitlab::UsageDataCounters::CycleAnalyticsCounter).to receive(:count).with(:views) + + get(:show, + params: { + namespace_id: project.namespace, + project_id: project + }) + + expect(response).to be_successful + end + end + describe 'cycle analytics not set up flag' do context 'with no data' do it 'is true' do @@ -20,7 +34,7 @@ describe Projects::CycleAnalyticsController do project_id: project }) - expect(response).to be_success + expect(response).to be_successful expect(assigns(:cycle_analytics_no_data)).to eq(true) end end @@ -41,7 +55,7 @@ describe Projects::CycleAnalyticsController do project_id: project }) - expect(response).to be_success + expect(response).to be_successful expect(assigns(:cycle_analytics_no_data)).to eq(false) end end diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb index fcd14f13863..ccad76eaddd 100644 --- a/spec/controllers/projects/deploy_keys_controller_spec.rb +++ b/spec/controllers/projects/deploy_keys_controller_spec.rb @@ -52,12 +52,10 @@ describe Projects::DeployKeysController do it 'returns json in a correct format' do get :index, params: params.merge(format: :json) - json = JSON.parse(response.body) - - expect(json.keys).to match_array(%w(enabled_keys available_project_keys public_keys)) - expect(json['enabled_keys'].count).to eq(1) - expect(json['available_project_keys'].count).to eq(1) - expect(json['public_keys'].count).to eq(1) + expect(json_response.keys).to match_array(%w(enabled_keys available_project_keys public_keys)) + expect(json_response['enabled_keys'].count).to eq(1) + expect(json_response['available_project_keys'].count).to eq(1) + expect(json_response['public_keys'].count).to eq(1) end end end diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 95417936df4..b9ee69a617b 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -41,34 +41,26 @@ describe Projects::DeploymentsController do describe 'GET #metrics' do let(:deployment) { create(:deployment, :success, project: project, environment: environment) } - before do - allow(controller).to receive(:deployment).and_return(deployment) - end - context 'when metrics are disabled' do - before do - allow(deployment).to receive(:has_metrics?).and_return false - end - it 'responds with not found' do - get :metrics, params: deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.to_param) expect(response).to be_not_found end end context 'when metrics are enabled' do - before do - allow(deployment).to receive(:has_metrics?).and_return true - end - context 'when environment has no metrics' do before do - expect(deployment).to receive(:metrics).and_return(nil) + expect_next_instance_of(DeploymentMetrics) do |deployment_metrics| + allow(deployment_metrics).to receive(:has_metrics?).and_return(true) + + expect(deployment_metrics).to receive(:metrics).and_return(nil) + end end it 'returns a empty response 204 resposne' do - get :metrics, params: deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.to_param) expect(response).to have_gitlab_http_status(204) expect(response.body).to eq('') end @@ -84,11 +76,15 @@ describe Projects::DeploymentsController do end before do - expect(deployment).to receive(:metrics).and_return(empty_metrics) + expect_next_instance_of(DeploymentMetrics) do |deployment_metrics| + allow(deployment_metrics).to receive(:has_metrics?).and_return(true) + + expect(deployment_metrics).to receive(:metrics).and_return(empty_metrics) + end end it 'returns a metrics JSON document' do - get :metrics, params: deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.to_param) expect(response).to be_ok expect(json_response['success']).to be(true) @@ -96,54 +92,32 @@ describe Projects::DeploymentsController do expect(json_response['last_update']).to eq(42) end end - - context 'when metrics service does not implement deployment metrics' do - before do - allow(deployment).to receive(:metrics).and_raise(NotImplementedError) - end - - it 'responds with not found' do - get :metrics, params: deployment_params(id: deployment.id) - - expect(response).to be_not_found - end - end end end describe 'GET #additional_metrics' do let(:deployment) { create(:deployment, :success, project: project, environment: environment) } - before do - allow(controller).to receive(:deployment).and_return(deployment) - end - context 'when metrics are disabled' do - before do - allow(deployment).to receive(:has_metrics?).and_return false - end - it 'responds with not found' do - get :metrics, params: deployment_params(id: deployment.id) + get :metrics, params: deployment_params(id: deployment.to_param) expect(response).to be_not_found end end context 'when metrics are enabled' do - let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } - - before do - allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter) - end - context 'when environment has no metrics' do before do - expect(deployment).to receive(:additional_metrics).and_return({}) + expect_next_instance_of(DeploymentMetrics) do |deployment_metrics| + allow(deployment_metrics).to receive(:has_metrics?).and_return(true) + + expect(deployment_metrics).to receive(:additional_metrics).and_return({}) + end end it 'returns a empty response 204 response' do - get :additional_metrics, params: deployment_params(id: deployment.id, format: :json) + get :additional_metrics, params: deployment_params(id: deployment.to_param, format: :json) expect(response).to have_gitlab_http_status(204) expect(response.body).to eq('') end @@ -159,11 +133,15 @@ describe Projects::DeploymentsController do end before do - expect(deployment).to receive(:additional_metrics).and_return(empty_metrics) + expect_next_instance_of(DeploymentMetrics) do |deployment_metrics| + allow(deployment_metrics).to receive(:has_metrics?).and_return(true) + + expect(deployment_metrics).to receive(:additional_metrics).and_return(empty_metrics) + end end it 'returns a metrics JSON document' do - get :additional_metrics, params: deployment_params(id: deployment.id, format: :json) + get :additional_metrics, params: deployment_params(id: deployment.to_param, format: :json) expect(response).to be_ok expect(json_response['success']).to be(true) diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb index 4c29162cd0f..e30b28a4bd5 100644 --- a/spec/controllers/projects/discussions_controller_spec.rb +++ b/spec/controllers/projects/discussions_controller_spec.rb @@ -112,7 +112,7 @@ describe Projects::DiscussionsController do it "returns the name of the resolving user" do post :resolve, params: request_params - expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name) + expect(json_response['resolved_by']['name']).to eq(user.name) end it "returns status 200" do @@ -135,7 +135,7 @@ describe Projects::DiscussionsController do it "returns truncated diff lines" do post :resolve, params: request_params - expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present + expect(json_response['truncated_diff_lines']).to be_present end end end diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb index fdef9bc5638..45328482ad7 100644 --- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb +++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb @@ -176,7 +176,7 @@ describe Projects::Environments::PrometheusApiController do def environment_params(params = {}) { id: environment.id.to_s, - namespace_id: project.namespace.name, + namespace_id: project.namespace.full_path, project_id: project.name, proxy_path: 'query', query: '1' diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 9699f2952f2..71ee1fd03bf 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Projects::EnvironmentsController do + include MetricsDashboardHelpers + set(:user) { create(:user) } set(:project) { create(:project) } @@ -445,117 +447,186 @@ describe Projects::EnvironmentsController do end end - describe 'metrics_dashboard' do - context 'when prometheus endpoint is disabled' do - before do - stub_feature_flags(environment_metrics_use_prometheus_endpoint: false) - end + describe 'GET #metrics_dashboard' do + shared_examples_for 'correctly formatted response' do |status_code| + it 'returns a json object with the correct keys' do + get :metrics_dashboard, params: environment_params(dashboard_params) - it 'responds with status code 403' do - get :metrics_dashboard, params: environment_params(format: :json) + # Exlcude `all_dashboards` to handle separately. + found_keys = json_response.keys - ['all_dashboards'] - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(status_code) + expect(found_keys).to contain_exactly(*expected_keys) end end - shared_examples_for '200 response' do |contains_all_dashboards: false| + shared_examples_for '200 response' do let(:expected_keys) { %w(dashboard status) } - before do - expected_keys << 'all_dashboards' if contains_all_dashboards - end - - it 'returns a json representation of the environment dashboard' do - get :metrics_dashboard, params: environment_params(dashboard_params) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.keys).to contain_exactly(*expected_keys) - expect(json_response['dashboard']).to be_an_instance_of(Hash) - end + it_behaves_like 'correctly formatted response', :ok end - shared_examples_for 'error response' do |status_code, contains_all_dashboards: false| + shared_examples_for 'error response' do |status_code| let(:expected_keys) { %w(message status) } - before do - expected_keys << 'all_dashboards' if contains_all_dashboards - end + it_behaves_like 'correctly formatted response', status_code + end - it 'returns an error response' do + shared_examples_for 'includes all dashboards' do + it 'includes info for all findable dashboard' do get :metrics_dashboard, params: environment_params(dashboard_params) - expect(response).to have_gitlab_http_status(status_code) - expect(json_response.keys).to contain_exactly(*expected_keys) + expect(json_response).to have_key('all_dashboards') + expect(json_response['all_dashboards']).to be_an_instance_of(Array) + expect(json_response['all_dashboards']).to all( include('path', 'default', 'display_name') ) end end - shared_examples_for 'has all dashboards' do - it 'includes an index of all available dashboards' do + shared_examples_for 'the default dashboard' do + all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards) + + it_behaves_like '200 response' + it_behaves_like 'includes all dashboards' if all_dashboards + + it 'is the default dashboard' do get :metrics_dashboard, params: environment_params(dashboard_params) - expect(json_response.keys).to include('all_dashboards') - expect(json_response['all_dashboards']).to be_an_instance_of(Array) - expect(json_response['all_dashboards']).to all( include('path', 'default') ) + expect(json_response['dashboard']['dashboard']).to eq('Environment metrics') end end - context 'when multiple dashboards is disabled' do - before do - stub_feature_flags(environment_metrics_show_multiple_dashboards: false) - end + shared_examples_for 'the specified dashboard' do |expected_dashboard| + it_behaves_like '200 response' + it_behaves_like 'includes all dashboards' + + it 'has the correct name' do + get :metrics_dashboard, params: environment_params(dashboard_params) - let(:dashboard_params) { { format: :json } } + dashboard_name = json_response['dashboard']['dashboard'] - it_behaves_like '200 response' + # 'Environment metrics' is the default dashboard. + expect(dashboard_name).not_to eq('Environment metrics') + expect(dashboard_name).to eq(expected_dashboard) + end - context 'when the dashboard could not be provided' do + context 'when the dashboard cannot not be processed' do before do allow(YAML).to receive(:safe_load).and_return({}) end it_behaves_like 'error response', :unprocessable_entity end + end + + shared_examples_for 'specified dashboard embed' do |expected_titles| + it_behaves_like '200 response' - context 'when a dashboard param is specified' do - let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } } + it 'contains only the specified charts' do + get :metrics_dashboard, params: environment_params(dashboard_params) + + dashboard = json_response['dashboard'] + panel_group = dashboard['panel_groups'].first + titles = panel_group['panels'].map { |panel| panel['title'] } - it_behaves_like '200 response' + expect(dashboard['dashboard']).to be_nil + expect(dashboard['panel_groups'].length).to eq 1 + expect(panel_group['group']).to be_nil + expect(titles).to eq expected_titles end end - context 'when multiple dashboards is enabled' do - let(:dashboard_params) { { format: :json } } + shared_examples_for 'the default dynamic dashboard' do + it_behaves_like 'specified dashboard embed', ['Memory Usage (Total)', 'Core Usage (Total)'] + end - it_behaves_like '200 response', contains_all_dashboards: true - it_behaves_like 'has all dashboards' + shared_examples_for 'dashboard can be specified' do + context 'when dashboard is specified' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:dashboard_params) { { format: :json, dashboard: dashboard_path } } - context 'when a dashboard could not be provided' do - before do - allow(YAML).to receive(:safe_load).and_return({}) + it_behaves_like 'error response', :not_found + + context 'when the project dashboard is available' do + let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') } + let(:project) { project_with_dashboard(dashboard_path, dashboard_yml) } + let(:environment) { create(:environment, name: 'production', project: project) } + + it_behaves_like 'the specified dashboard', 'Test Dashboard' end - it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true - it_behaves_like 'has all dashboards' + context 'when the specified dashboard is the default dashboard' do + let(:dashboard_path) { system_dashboard_path } + + it_behaves_like 'the default dashboard' + end end + end - context 'when a dashboard param is specified' do - let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } } + shared_examples_for 'dashboard can be embedded' do + context 'when the embedded flag is included' do + let(:dashboard_params) { { format: :json, embedded: true } } - context 'when the dashboard is available' do - let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') } - let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } } - let(:project) { create(:project, :custom_repo, files: dashboard_file) } - let(:environment) { create(:environment, name: 'production', project: project) } + it_behaves_like 'the default dynamic dashboard' - it_behaves_like '200 response', contains_all_dashboards: true - it_behaves_like 'has all dashboards' + context 'when incomplete dashboard params are provided' do + let(:dashboard_params) { { format: :json, embedded: true, title: 'Title' } } + + # The title param should be ignored. + it_behaves_like 'the default dynamic dashboard' end - context 'when the dashboard does not exist' do - it_behaves_like 'error response', :not_found, contains_all_dashboards: true - it_behaves_like 'has all dashboards' + context 'when invalid params are provided' do + let(:dashboard_params) { { format: :json, embedded: true, metric_id: 16 } } + + # The superfluous param should be ignored. + it_behaves_like 'the default dynamic dashboard' end + + context 'when the dashboard is correctly specified' do + let(:dashboard_params) do + { + format: :json, + embedded: true, + dashboard: system_dashboard_path, + group: business_metric_title, + title: 'title', + y_label: 'y_label' + } + end + + it_behaves_like 'error response', :not_found + + context 'and exists' do + let!(:metric) { create(:prometheus_metric, project: project) } + + it_behaves_like 'specified dashboard embed', ['title'] + end + end + end + end + + shared_examples_for 'dashboard cannot be specified' do + context 'when dashboard is specified' do + let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } } + + it_behaves_like 'the default dashboard' + end + end + + let(:dashboard_params) { { format: :json } } + + it_behaves_like 'the default dashboard' + it_behaves_like 'dashboard can be specified' + it_behaves_like 'dashboard can be embedded' + + context 'when multiple dashboards is disabled' do + before do + stub_feature_flags(environment_metrics_show_multiple_dashboards: false) end + + it_behaves_like 'the default dashboard' + it_behaves_like 'dashboard cannot be specified' + it_behaves_like 'dashboard can be embedded' end end diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb index 844c61f1ace..d11ef24ef96 100644 --- a/spec/controllers/projects/error_tracking_controller_spec.rb +++ b/spec/controllers/projects/error_tracking_controller_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require 'spec_helper' describe Projects::ErrorTrackingController do set(:project) { create(:project) } diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb index 538dbb5ad0b..a493985f8a0 100644 --- a/spec/controllers/projects/find_file_controller_spec.rb +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -53,10 +53,9 @@ describe Projects::FindFileController do it 'returns an array of file path list' do go - json = JSON.parse(response.body) is_expected.to respond_with(:success) - expect(json).not_to eq(nil) - expect(json.length).to be >= 0 + expect(json_response).not_to eq(nil) + expect(json_response.length).to be >= 0 end end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 3423fdf4c41..5ac5279e997 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -115,24 +115,34 @@ describe Projects::ForksController do end describe 'POST create' do - def post_create + def post_create(params = {}) post :create, params: { namespace_id: project.namespace, project_id: project, namespace_key: user.namespace.id - } + }.merge(params) end context 'when user is signed in' do - it 'responds with status 302' do + before do sign_in(user) + end + it 'responds with status 302' do post_create expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(namespace_project_import_path(user.namespace, project)) end + + it 'passes continue params to the redirect' do + continue_params = { to: '/-/ide/project/path', notice: 'message' } + post_create continue: continue_params + + expect(response).to have_gitlab_http_status(302) + expect(response).to redirect_to(namespace_project_import_path(user.namespace, project, continue: continue_params)) + end end context 'when user is not signed in' do diff --git a/spec/controllers/projects/git_http_controller_spec.rb b/spec/controllers/projects/git_http_controller_spec.rb index bf099e8deeb..88fa2236e33 100644 --- a/spec/controllers/projects/git_http_controller_spec.rb +++ b/spec/controllers/projects/git_http_controller_spec.rb @@ -12,4 +12,15 @@ describe Projects::GitHttpController do expect(response.status).to eq(403) end end + + describe 'GET #info_refs' do + it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do + stub_application_setting(enabled_git_access_protocol: 'ssh') + project = create(:project, :public, :repository) + + get :info_refs, params: { service: 'git-upload-pack', namespace_id: project.namespace.to_param, project_id: project.path + '.git' } + + expect(response.status).to eq(401) + end + end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 32607fc5f56..fab47aa4701 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Projects::IssuesController do + include ProjectForksHelper + let(:project) { create(:project) } let(:user) { create(:user) } let(:issue) { create(:issue, project: project) } @@ -249,15 +251,13 @@ describe Projects::IssuesController do end end - describe 'Redirect after sign in' do + # This spec runs as a request-style spec in order to invoke the + # Rails router. A controller-style spec matches the wrong route, and + # session['user_return_to'] becomes incorrect. + describe 'Redirect after sign in', type: :request do context 'with an AJAX request' do it 'does not store the visited URL' do - get :show, params: { - format: :json, - namespace_id: project.namespace, - project_id: project, - id: issue.iid - }, xhr: true + get project_issue_path(project, issue), xhr: true expect(session['user_return_to']).to be_blank end @@ -265,14 +265,9 @@ describe Projects::IssuesController do context 'without an AJAX request' do it 'stores the visited URL' do - get :show, - params: { - namespace_id: project.namespace.to_param, - project_id: project, - id: issue.iid - } + get project_issue_path(project, issue) - expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}") + expect(session['user_return_to']).to eq(project_issue_path(project, issue)) end end end @@ -320,6 +315,90 @@ describe Projects::IssuesController do end end + describe 'PUT #reorder' do + let(:group) { create(:group, projects: [project]) } + let!(:issue1) { create(:issue, project: project, relative_position: 10) } + let!(:issue2) { create(:issue, project: project, relative_position: 20) } + let!(:issue3) { create(:issue, project: project, relative_position: 30) } + + before do + sign_in(user) + end + + context 'when user has access' do + before do + project.add_developer(user) + end + + context 'with valid params' do + it 'reorders issues and returns a successful 200 response' do + reorder_issue(issue1, + move_after_id: issue2.id, + move_before_id: issue3.id, + group_full_path: group.full_path) + + [issue1, issue2, issue3].map(&:reload) + + expect(response).to have_gitlab_http_status(200) + expect(issue1.relative_position) + .to be_between(issue2.relative_position, issue3.relative_position) + end + end + + context 'with invalid params' do + it 'returns a unprocessable entity 422 response for invalid move ids' do + reorder_issue(issue1, move_after_id: 99, move_before_id: 999) + + expect(response).to have_gitlab_http_status(422) + end + + it 'returns a not found 404 response for invalid issue id' do + reorder_issue(object_double(issue1, iid: 999), + move_after_id: issue2.id, + move_before_id: issue3.id) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a unprocessable entity 422 response for issues not in group' do + another_group = create(:group) + + reorder_issue(issue1, + move_after_id: issue2.id, + move_before_id: issue3.id, + group_full_path: another_group.full_path) + + expect(response).to have_gitlab_http_status(422) + end + end + end + + context 'with unauthorized user' do + before do + project.add_guest(user) + end + + it 'responds with 404' do + reorder_issue(issue1, move_after_id: issue2.id, move_before_id: issue3.id) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + def reorder_issue(issue, move_after_id: nil, move_before_id: nil, group_full_path: nil) + put :reorder, + params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: issue.iid, + move_after_id: move_after_id, + move_before_id: move_before_id, + group_full_path: group_full_path + }, + format: :json + end + end + describe 'PUT #update' do subject do put :update, @@ -358,7 +437,7 @@ describe Projects::IssuesController do it 'renders json with recaptcha_html' do subject - expect(JSON.parse(response.body)).to have_key('recaptcha_html') + expect(json_response).to have_key('recaptcha_html') end end end @@ -398,10 +477,8 @@ describe Projects::IssuesController do it 'returns last edited time' do go(id: issue.iid) - data = JSON.parse(response.body) - - expect(data).to include('updated_at') - expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601) + expect(json_response).to include('updated_at') + expect(json_response['updated_at']).to eq(issue.last_edited_at.to_time.iso8601) end end @@ -434,10 +511,8 @@ describe Projects::IssuesController do it 'returns the necessary data' do go(id: issue.iid) - data = JSON.parse(response.body) - - expect(data).to include('title_text', 'description', 'description_text') - expect(data).to include('task_status', 'lock_version') + expect(json_response).to include('title_text', 'description', 'description_text') + expect(json_response).to include('task_status', 'lock_version') end end end @@ -606,9 +681,7 @@ describe Projects::IssuesController do update_issue(issue_params: { assignee_ids: [assignee.id] }) - body = JSON.parse(response.body) - - expect(body['assignees'].first.keys) + expect(json_response['assignees'].first.keys) .to match_array(%w(id name username avatar_url state web_url)) end end @@ -1046,6 +1119,7 @@ describe Projects::IssuesController do end describe 'POST create_merge_request' do + let(:target_project_id) { nil } let(:project) { create(:project, :repository, :public) } before do @@ -1079,13 +1153,42 @@ describe Projects::IssuesController do expect(response).to have_gitlab_http_status(404) end + context 'target_project_id is set' do + let(:target_project) { fork_project(project, user, repository: true) } + let(:target_project_id) { target_project.id } + + context 'create_confidential_merge_request feature is enabled' do + before do + stub_feature_flags(create_confidential_merge_request: true) + end + + it 'creates a new merge request' do + expect { create_merge_request }.to change(target_project.merge_requests, :count).by(1) + end + end + + context 'create_confidential_merge_request feature is disabled' do + before do + stub_feature_flags(create_confidential_merge_request: false) + end + + it 'creates a new merge request' do + expect { create_merge_request }.to change(project.merge_requests, :count).by(1) + end + end + end + def create_merge_request - post :create_merge_request, params: { - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: issue.to_param - }, - format: :json + post( + :create_merge_request, + params: { + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: issue.to_param, + target_project_id: target_project_id + }, + format: :json + ) end end @@ -1150,6 +1253,28 @@ describe Projects::IssuesController do sign_in(user) end + context do + it_behaves_like 'discussions provider' do + let!(:author) { create(:user) } + let!(:project) { create(:project) } + + let!(:issue) { create(:issue, project: project, author: user) } + + let!(:note_on_issue1) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) } + let!(:note_on_issue2) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) } + + let(:requested_iid) { issue.iid } + let(:expected_discussion_count) { 3 } + let(:expected_discussion_ids) do + [ + issue.notes.first.discussion_id, + note_on_issue1.discussion_id, + note_on_issue2.discussion_id + ] + end + end + end + it 'returns discussion json' do get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } @@ -1198,7 +1323,7 @@ describe Projects::IssuesController do it 'filters notes that the user should not see' do get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } - expect(JSON.parse(response.body).count).to eq(1) + expect(json_response.count).to eq(1) end it 'does not result in N+1 queries' do diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 490e9841492..f076a5e769f 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -73,21 +73,27 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end context 'number of queries' do + render_views + before do Ci::Build::AVAILABLE_STATUSES.each do |status| create_job(status, status) end + + allow(Appearance).to receive(:current_without_cache) + .and_return(nil) end it 'verifies number of queries', :request_store do - recorded = ActiveRecord::QueryRecorder.new { get_index } - expect(recorded.count).to be_within(5).of(7) + expect { get_index }.not_to be_n_plus_1_query.with_threshold(3) end def create_job(name, status) - pipeline = create(:ci_pipeline, project: project) + user = create(:user) + pipeline = create(:ci_pipeline, project: project, user: user) create(:ci_build, :tags, :triggered, :artifacts, - pipeline: pipeline, name: name, status: status) + pipeline: pipeline, name: name, status: status, + user: user) end end @@ -152,7 +158,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do get_show_json json_response.dig('pipeline', 'details', 'stages').tap do |stages| - expect(stages.map(&:keys).flatten) + expect(stages.flat_map(&:keys)) .to eq %w[name title status path dropdown_path] end end @@ -540,7 +546,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq job.id expect(json_response['status']).to eq job.status - expect(json_response['html']).to eq('BUILD TRACE') + expect(json_response['html']).to eq('<span>BUILD TRACE</span>') end end @@ -670,6 +676,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end describe 'POST play' do + let(:variable_attributes) { [] } + before do project.add_developer(user) @@ -692,6 +700,14 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do it 'transits to pending' do expect(job.reload).to be_pending end + + context 'when job variables are specified' do + let(:variable_attributes) { [{ key: 'first', secret_value: 'first' }] } + + it 'assigns the job variables' do + expect(job.reload.job_variables.map(&:key)).to contain_exactly('first') + end + end end context 'when job is not playable' do @@ -706,7 +722,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do post :play, params: { namespace_id: project.namespace, project_id: project, - id: job.id + id: job.id, + job_variables_attributes: variable_attributes } end end diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb new file mode 100644 index 00000000000..818cf794ec6 --- /dev/null +++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::MergeRequests::ContentController do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:merge_request) { create(:merge_request, target_project: project, source_project: project) } + + before do + sign_in(user) + end + + def do_request(action = :cached_widget) + get action, params: { + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + format: :json + } + end + + context 'user has access to the project' do + before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + + project.add_maintainer(user) + end + + describe 'GET cached_widget' do + it 'renders widget MR entity as json' do + do_request + + expect(response).to match_response_schema('entities/merge_request_poll_cached_widget') + end + + it 'closes an MR with moved source project' do + merge_request.update_column(:source_project_id, nil) + + expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false) + end + end + + describe 'GET widget' do + it 'checks whether the MR can be merged' do + controller.instance_variable_set(:@merge_request, merge_request) + + expect(merge_request).to receive(:check_mergeability) + + do_request(:widget) + end + + context 'merged merge request' do + let(:merge_request) do + create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project) + end + + it 'renders widget MR entity as json' do + do_request(:widget) + + expect(response).to match_response_schema('entities/merge_request_poll_widget') + end + end + end + end + + context 'user does not have access to the project' do + describe 'GET cached_widget' do + it 'returns 404' do + do_request + + expect(response).to have_http_status(:not_found) + end + end + + describe 'GET widget' do + it 'returns 404' do + do_request(:widget) + + expect(response).to have_http_status(:not_found) + end + end + end +end diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index 5fefad86ef3..ce977f26ec6 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -28,7 +28,7 @@ describe Projects::MergeRequests::CreationsController do it 'renders new merge request widget template' do get :new, params: get_diff_params - expect(response).to be_success + expect(response).to be_successful end end @@ -56,7 +56,7 @@ describe Projects::MergeRequests::CreationsController do it 'limits total commits' do get :new, params: large_diff_params - expect(response).to be_success + expect(response).to be_successful total = assigns(:total_commit_count) expect(assigns(:commits)).to be_an Array @@ -70,7 +70,7 @@ describe Projects::MergeRequests::CreationsController do it 'shows total commits' do get :new, params: large_diff_params - expect(response).to be_success + expect(response).to be_successful total = assigns(:total_commit_count) expect(assigns(:commits)).to be_an CommitCollection @@ -89,7 +89,7 @@ describe Projects::MergeRequests::CreationsController do get :diffs, params: get_diff_params.merge(format: 'json') - expect(response).to be_success + expect(response).to be_successful expect(assigns[:diffs]).to be_nil end end @@ -212,4 +212,46 @@ describe Projects::MergeRequests::CreationsController do expect(response).to have_gitlab_http_status(200) end end + + describe 'POST create' do + let(:params) do + { + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + title: 'Test merge request', + source_branch: 'remove-submodule', + target_branch: 'master' + } + } + end + + it 'creates merge request' do + expect do + post_request(params) + end.to change { MergeRequest.count }.by(1) + end + + context 'when the merge request is not created from the web ide' do + it 'counter is not increased' do + expect(Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_merge_requests_count) + + post_request(params) + end + end + + context 'when the merge request is created from the web ide' do + let(:nav_source) { { nav_source: 'webide' } } + + it 'counter is increased' do + expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_merge_requests_count) + + post_request(params.merge(nav_source)) + end + end + + def post_request(merge_request_params) + post :create, params: merge_request_params + end + end end diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 13a28b738ca..ac3e9901123 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -66,7 +66,7 @@ describe Projects::MergeRequests::DiffsController do end it 'renders' do - expect(response).to be_success + expect(response).to be_successful expect(response.body).to have_content('Subproject commit') end end @@ -112,7 +112,7 @@ describe Projects::MergeRequests::DiffsController do it 'only renders the diffs for the path given' do diff_for_path(old_path: existing_path, new_path: existing_path) - paths = JSON.parse(response.body)["diff_files"].map { |file| file['new_path'] } + paths = json_response["diff_files"].map { |file| file['new_path'] } expect(paths).to include(existing_path) end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 34cbf0c8723..11b1eaf11b7 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -57,7 +57,7 @@ describe Projects::MergeRequestsController do go(format: :html) - expect(response).to be_success + expect(response).to be_successful end end @@ -66,7 +66,7 @@ describe Projects::MergeRequestsController do go(format: :html) - expect(response).to be_success + expect(response).to be_successful end context "that is invalid" do @@ -75,7 +75,7 @@ describe Projects::MergeRequestsController do it "renders merge request page" do go(format: :html) - expect(response).to be_success + expect(response).to be_successful end end end @@ -124,7 +124,7 @@ describe Projects::MergeRequestsController do it "renders merge request page" do go(format: :json) - expect(response).to be_success + expect(response).to be_successful end end end @@ -242,9 +242,7 @@ describe Projects::MergeRequestsController do update_merge_request({ assignee_ids: [assignee.id] }, format: :json) - body = JSON.parse(response.body) - - expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url))) + expect(json_response['assignees']).to all(include(*%w(name username avatar_url id state web_url))) end end @@ -623,10 +621,100 @@ describe Projects::MergeRequestsController do format: :json end - it 'responds with serialized pipelines' do - expect(json_response['pipelines']).not_to be_empty - expect(json_response['count']['all']).to eq 1 - expect(response).to include_pagination_headers + context 'with "enabled" builds on a public project' do + let(:project) { create(:project, :repository, :public) } + + context 'for a project owner' do + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + + context 'for an unassociated user' do + let(:user) { create :user } + + it 'responds with no pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + end + + context 'with private builds on a public project' do + let(:project) { create(:project, :repository, :public, :builds_private) } + + context 'for a project owner' do + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + + context 'for an unassociated user' do + let(:user) { create :user } + + it 'responds with no pipelines' do + expect(json_response['pipelines']).to be_empty + expect(json_response['count']['all']).to eq(0) + expect(response).to include_pagination_headers + end + end + + context 'from a project fork' do + let(:fork_user) { create :user } + let(:forked_project) { fork_project(project, fork_user, repository: true) } # Forked project carries over :builds_private + let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: forked_project) } + + context 'with private builds' do + context 'for the target project member' do + it 'does not respond with serialized pipelines' do + expect(json_response['pipelines']).to be_empty + expect(json_response['count']['all']).to eq(0) + expect(response).to include_pagination_headers + end + end + + context 'for the source project member' do + let(:user) { fork_user } + + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + end + + context 'with public builds' do + let(:forked_project) do + fork_project(project, fork_user, repository: true).tap do |new_project| + new_project.project_feature.update(builds_access_level: ProjectFeature::ENABLED) + end + end + + context 'for the target project member' do + it 'does not respond with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + + context 'for the source project member' do + let(:user) { fork_user } + + it 'responds with serialized pipelines' do + expect(json_response['pipelines']).to be_present + expect(json_response['count']['all']).to eq(1) + expect(response).to include_pagination_headers + end + end + end + end end end @@ -878,17 +966,79 @@ describe Projects::MergeRequestsController do expect(control_count).to be <= 137 end - def get_ci_environments_status(extra_params = {}) - params = { - namespace_id: merge_request.project.namespace.to_param, - project_id: merge_request.project, - id: merge_request.iid, - format: 'json' - } + it 'has no N+1 SQL issues for environments', :request_store, retry: 0 do + # First run to insert test data from lets, which does take up some 30 queries + get_ci_environments_status + + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { get_ci_environments_status }.count + + environment2 = create(:environment, project: forked) + create(:deployment, :succeed, environment: environment2, sha: sha, ref: 'master', deployable: build) + + # TODO address the last 5 queries + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63952 (5 queries) + leeway = 5 + expect { get_ci_environments_status }.not_to exceed_all_query_limit(control_count + leeway) + end + end + + context 'when a merge request has multiple environments with deployments' do + let(:sha) { merge_request.diff_head_sha } + let(:ref) { merge_request.source_branch } + + let!(:build) { create(:ci_build, pipeline: pipeline) } + let!(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } + let!(:environment) { create(:environment, name: 'env_a', project: project) } + let!(:another_environment) { create(:environment, name: 'env_b', project: project) } + + before do + merge_request.update_head_pipeline + + create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build) + create(:deployment, :succeed, environment: another_environment, sha: sha, ref: ref, deployable: build) + end + + it 'exposes multiple environment statuses' do + get_ci_environments_status + + expect(json_response.count).to eq 2 + end + + context 'when route map is not present in the project' do + it 'does not have N+1 Gitaly requests for environments', :request_store do + expect(merge_request).to be_present + + expect { get_ci_environments_status } + .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1) + end + end - get :ci_environments_status, params: params.merge(extra_params) + context 'when there is route map present in a project' do + before do + allow_any_instance_of(EnvironmentStatus) + .to receive(:has_route_map?) + .and_return(true) + end + + it 'does not have N+1 Gitaly requests for diff files', :request_store do + expect(merge_request.merge_request_diff.merge_request_diff_files).to be_many + + expect { get_ci_environments_status } + .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1) + end end end + + def get_ci_environments_status(extra_params = {}) + params = { + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project, + id: merge_request.iid, + format: 'json' + } + + get :ci_environments_status, params: params.merge(extra_params) + end end describe 'GET pipeline_status.json' do @@ -1001,6 +1151,8 @@ describe Projects::MergeRequestsController do before do project.add_developer(user) sign_in(user) + + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original end it 'returns 200' do @@ -1058,6 +1210,22 @@ describe Projects::MergeRequestsController do end end end + + context do + it_behaves_like 'discussions provider' do + let!(:author) { create(:user) } + let!(:project) { create(:project) } + + let!(:merge_request) { create(:merge_request, source_project: project) } + + let!(:mr_note1) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) } + let!(:mr_note2) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) } + + let(:requested_iid) { merge_request.iid } + let(:expected_discussion_count) { 2 } + let(:expected_discussion_ids) { [mr_note1.discussion_id, mr_note2.discussion_id] } + end + end end describe 'GET edit' do diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 767cee7d54a..cbf9d437909 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -115,7 +115,7 @@ describe Projects::MilestonesController do end end - context 'with nested groups', :nested_groups do + context 'with nested groups' do let!(:subgroup) { create(:group, :public, parent: group) } let!(:subgroup_milestone) { create(:milestone, group: subgroup) } @@ -139,7 +139,7 @@ describe Projects::MilestonesController do expect(issue.milestone_id).to eq(milestone.id) delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }, format: :js - expect(response).to be_success + expect(response).to be_successful expect(Event.recent.first.action).to eq(Event::DESTROYED) diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 6ec84f5c528..9ab565dc2e8 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -29,7 +29,7 @@ describe Projects::NotesController do } end - let(:parsed_response) { JSON.parse(response.body).with_indifferent_access } + let(:parsed_response) { json_response.with_indifferent_access } let(:note_json) { parsed_response[:notes].first } before do @@ -43,7 +43,7 @@ describe Projects::NotesController do request.headers['X-Last-Fetched-At'] = last_fetched_at expect(NotesFinder).to receive(:new) - .with(anything, anything, hash_including(last_fetched_at: last_fetched_at)) + .with(anything, hash_including(last_fetched_at: last_fetched_at)) .and_call_original get :index, params: request_params @@ -252,7 +252,7 @@ describe Projects::NotesController do before do service_params = ActionController::Parameters.new({ note: 'some note', - noteable_id: merge_request.id.to_s, + noteable_id: merge_request.id, noteable_type: 'MergeRequest', commit_id: nil, merge_request_diff_head_sha: 'sha' @@ -614,7 +614,7 @@ describe Projects::NotesController do it "returns the name of the resolving user" do post :resolve, params: request_params.merge(html: true) - expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name) + expect(json_response["resolved_by"]).to eq(user.name) end it "returns status 200" do diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb index ff3afd51cd8..032f4f1418f 100644 --- a/spec/controllers/projects/pages_domains_controller_spec.rb +++ b/spec/controllers/projects/pages_domains_controller_spec.rb @@ -15,7 +15,10 @@ describe Projects::PagesDomainsController do end let(:pages_domain_params) do - build(:pages_domain, domain: 'my.otherdomain.com').slice(:key, :certificate, :domain) + attributes_for(:pages_domain, domain: 'my.otherdomain.com').slice(:key, :certificate, :domain).tap do |params| + params[:user_provided_key] = params.delete(:key) + params[:user_provided_certificate] = params.delete(:certificate) + end end before do @@ -84,48 +87,59 @@ describe Projects::PagesDomainsController do controller.instance_variable_set(:@domain, pages_domain) end - let(:pages_domain_params) do - attributes_for(:pages_domain).slice(:key, :certificate) - end - let(:params) do request_params.merge(id: pages_domain.domain, pages_domain: pages_domain_params) end - it 'updates the domain' do - expect(pages_domain) - .to receive(:update) - .with(ActionController::Parameters.new(pages_domain_params).permit!) - .and_return(true) + context 'with valid params' do + let(:pages_domain_params) do + attributes_for(:pages_domain, :with_trusted_chain).slice(:key, :certificate).tap do |params| + params[:user_provided_key] = params.delete(:key) + params[:user_provided_certificate] = params.delete(:certificate) + end + end + + it 'updates the domain' do + expect do + patch(:update, params: params) + end.to change { pages_domain.reload.certificate }.to(pages_domain_params[:user_provided_certificate]) + end + + it 'redirects to the project page' do + patch(:update, params: params) - patch(:update, params: params) + expect(flash[:notice]).to eq 'Domain was updated' + expect(response).to redirect_to(project_pages_path(project)) + end end - it 'redirects to the project page' do - patch(:update, params: params) + context 'with key parameter' do + before do + pages_domain.update!(key: nil, certificate: nil, certificate_source: 'gitlab_provided') + end - expect(flash[:notice]).to eq 'Domain was updated' - expect(response).to redirect_to(project_pages_path(project)) + it 'marks certificate as provided by user' do + expect do + patch(:update, params: params) + end.to change { pages_domain.reload.certificate_source }.from('gitlab_provided').to('user_provided') + end end context 'the domain is invalid' do - it 'renders the edit action' do - allow(pages_domain).to receive(:update).and_return(false) + let(:pages_domain_params) { { user_provided_certificate: 'blabla' } } + it 'renders the edit action' do patch(:update, params: params) expect(response).to render_template('edit') end end - context 'the parameters include the domain' do - it 'renders 400 Bad Request' do - expect(pages_domain) - .to receive(:update) - .with(hash_not_including(:domain)) - .and_return(true) - - patch(:update, params: params.deep_merge(pages_domain: { domain: 'abc' })) + context 'when parameters include the domain' do + it 'does not update domain' do + expect do + patch(:update, params: params.deep_merge(pages_domain: { domain: 'abc' })) + end.not_to change { pages_domain.reload.domain } end end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 9a50ea79f5e..089d06e11eb 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -177,18 +177,22 @@ describe Projects::PipelinesController do end it 'does not perform N + 1 queries' do + # Set up all required variables + get_pipeline_json + control_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count - create_build('test', 1, 'rspec 1') - create_build('test', 1, 'spinach 0') - create_build('test', 1, 'spinach 1') - create_build('test', 1, 'audit') - create_build('post deploy', 3, 'pages 1') - create_build('post deploy', 3, 'pages 2') + first_build = pipeline.builds.first + first_build.tag_list << [:hello, :world] + create(:deployment, deployable: first_build) + + second_build = pipeline.builds.second + second_build.tag_list << [:docker, :ruby] + create(:deployment, deployable: second_build) new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count - expect(new_count).to be_within(12).of(control_count) + expect(new_count).to be_within(1).of(control_count) end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 4141e41c7a7..5130e26c928 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -158,7 +158,7 @@ describe Projects::ProjectMembersController do id: member }, xhr: true - expect(response).to be_success + expect(response).to be_successful expect(project.members).not_to include member end end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 97acd47b4da..8b43d1264b2 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Projects::RawController do + include RepoHelpers + let(:project) { create(:project, :public, :repository) } describe 'GET #show' do @@ -46,5 +48,98 @@ describe Projects::RawController do let(:filename) { 'lfs_object.iso' } let(:filepath) { "be93687/files/lfs/#{filename}" } end + + context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do + let(:file_path) { 'master/README.md' } + + before do + stub_application_setting(raw_blob_request_limit: 5) + end + + it 'prevents from accessing the raw file' do + execute_raw_requests(requests: 6, project: project, file_path: file_path) + + expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.') + expect(response).to have_gitlab_http_status(429) + end + + it 'logs the event on auth.log' do + attributes = { + message: 'Action_Rate_Limiter_Request', + env: :raw_blob_request_limit, + remote_ip: '0.0.0.0', + request_method: 'GET', + path: "/#{project.full_path}/raw/#{file_path}" + } + + expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once + + execute_raw_requests(requests: 6, project: project, file_path: file_path) + end + + context 'when the request uses a different version of a commit' do + it 'prevents from accessing the raw file' do + # 3 times with the normal sha + commit_sha = project.repository.commit.sha + file_path = "#{commit_sha}/README.md" + + execute_raw_requests(requests: 3, project: project, file_path: file_path) + + # 3 times with the modified version + modified_sha = commit_sha.gsub(commit_sha[0..5], commit_sha[0..5].upcase) + modified_path = "#{modified_sha}/README.md" + + execute_raw_requests(requests: 3, project: project, file_path: modified_path) + + expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.') + expect(response).to have_gitlab_http_status(429) + end + end + + context 'when the throttling has been disabled' do + before do + stub_application_setting(raw_blob_request_limit: 0) + end + + it 'does not prevent from accessing the raw file' do + execute_raw_requests(requests: 10, project: project, file_path: file_path) + + expect(response).to have_gitlab_http_status(200) + end + end + + context 'with case-sensitive files' do + it 'prevents from accessing the specific file' do + create_file_in_repo(project, 'master', 'master', 'readme.md', 'Add readme.md') + create_file_in_repo(project, 'master', 'master', 'README.md', 'Add README.md') + + commit_sha = project.repository.commit.sha + file_path = "#{commit_sha}/readme.md" + + # Accessing downcase version of readme + execute_raw_requests(requests: 6, project: project, file_path: file_path) + + expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.') + expect(response).to have_gitlab_http_status(429) + + # Accessing upcase version of readme + file_path = "#{commit_sha}/README.md" + + execute_raw_requests(requests: 1, project: project, file_path: file_path) + + expect(response).to have_gitlab_http_status(200) + end + end + end + end + + def execute_raw_requests(requests:, project:, file_path:) + requests.times do + get :show, params: { + namespace_id: project.namespace, + project_id: project, + id: file_path + } + end end end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index 6db98f2428b..646c7a7db7c 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -49,7 +49,7 @@ describe Projects::RefsController do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original xhr_get(:js) - expect(response).to be_success + expect(response).to be_successful end it 'renders JSON' do @@ -57,7 +57,7 @@ describe Projects::RefsController do xhr_get(:json) - expect(response).to be_success + expect(response).to be_successful expect(json_response).to be_kind_of(Array) end end diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb index ff35139ae2e..c6e063d8229 100644 --- a/spec/controllers/projects/registry/tags_controller_spec.rb +++ b/spec/controllers/projects/registry/tags_controller_spec.rb @@ -113,4 +113,37 @@ describe Projects::Registry::TagsController do format: :json end end + + describe 'POST bulk_destroy' do + context 'when user has access to registry' do + before do + project.add_developer(user) + end + + context 'when there is matching tag present' do + before do + stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.]) + end + + it 'makes it possible to delete tags in bulk' do + allow_any_instance_of(ContainerRegistry::Tag).to receive(:delete) { |*args| ContainerRegistry::Tag.delete(*args) } + expect(ContainerRegistry::Tag).to receive(:delete).exactly(2).times + + bulk_destroy_tags(['rc1', 'test.']) + end + end + end + + private + + def bulk_destroy_tags(names) + post :bulk_destroy, params: { + namespace_id: project.namespace, + project_id: project, + repository_id: repository, + ids: names + }, + format: :json + end + end end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 8fca9e680dd..fcab4d73dca 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -77,6 +77,53 @@ describe Projects::RepositoriesController do expect(response).to have_gitlab_http_status(404) end end + + describe 'caching' do + it 'sets appropriate caching headers' do + get_archive + + expect(response).to have_gitlab_http_status(200) + expect(response.header['ETag']).to be_present + expect(response.header['Cache-Control']).to include('max-age=60, private') + end + + context 'when project is public' do + let(:project) { create(:project, :repository, :public) } + + it 'sets appropriate caching headers' do + get_archive + + expect(response).to have_gitlab_http_status(200) + expect(response.header['ETag']).to be_present + expect(response.header['Cache-Control']).to include('max-age=60, public') + end + end + + context 'when ref is a commit SHA' do + it 'max-age is set to 3600 in Cache-Control header' do + get_archive('ddd0f15ae83993f5cb66a927a28673882e99100b') + + expect(response).to have_gitlab_http_status(200) + expect(response.header['Cache-Control']).to include('max-age=3600') + end + end + + context 'when If-None-Modified header is set' do + it 'returns a 304 status' do + # Get the archive cached first + get_archive + + request.headers['If-None-Match'] = response.headers['ETag'] + get_archive + + expect(response).to have_gitlab_http_status(304) + end + end + + def get_archive(id = 'feature') + get :archive, params: { namespace_id: project.namespace, project_id: project, id: id }, format: 'zip' + end + end end end end diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 18c594acae0..9f1ef3a4be8 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -10,12 +10,16 @@ describe Projects::Serverless::FunctionsController do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { cluster.platform_kubernetes } let(:project) { cluster.project } + let(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } + let(:knative_services_finder) { environment.knative_services_finder } let(:namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, cluster_project: cluster.cluster_project, - project: cluster.cluster_project.project) + project: cluster.cluster_project.project, + environment: environment) end before do @@ -47,12 +51,11 @@ describe Projects::Serverless::FunctionsController do end context 'when cache is ready' do - let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) } let(:knative_state) { true } before do - allow_any_instance_of(Clusters::Cluster) - .to receive(:knative_services_finder) + allow(Clusters::KnativeServicesFinder) + .to receive(:new) .and_return(knative_services_finder) synchronous_reactive_cache(knative_services_finder) stub_kubeclient_service_pods( @@ -107,12 +110,12 @@ describe Projects::Serverless::FunctionsController do context 'valid data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods - stub_reactive_cache(cluster.knative_services_finder(project), + stub_reactive_cache(knative_services_finder, { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] }, - *cluster.knative_services_finder(project).cache_args) + *knative_services_finder.cache_args) end it 'has a valid function name' do @@ -140,12 +143,12 @@ describe Projects::Serverless::FunctionsController do describe 'GET #index with data', :use_clean_rails_memory_store_caching do before do stub_kubeclient_service_pods - stub_reactive_cache(cluster.knative_services_finder(project), + stub_reactive_cache(knative_services_finder, { services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"], pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"] }, - *cluster.knative_services_finder(project).cache_args) + *knative_services_finder.cache_args) end it 'has data' do diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 3608d175d50..563b61962cf 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -128,7 +128,7 @@ describe Projects::ServicesController do params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } expect(response).to redirect_to(project_settings_integrations_path(project)) - expect(flash[:notice]).to eq 'JIRA activated.' + expect(flash[:notice]).to eq 'Jira activated.' end end @@ -137,53 +137,29 @@ describe Projects::ServicesController do put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } } - expect(flash[:notice]).to eq 'JIRA settings saved, but not activated.' + expect(flash[:notice]).to eq 'Jira settings saved, but not activated.' end end - context 'with a deprecated service' do - let(:service) { create(:kubernetes_service, project: project) } - - before do - put :update, - params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } } - end - - it 'does not update the service' do - service.reload - expect(service.namespace).not_to eq('updated_namespace') - end - end - - context 'when activating JIRA service from a template' do + context 'when activating Jira service from a template' do let(:template_service) { create(:jira_service, project: project, template: true) } - it 'activate JIRA service from template' do + it 'activate Jira service from template' do put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } - expect(flash[:notice]).to eq 'JIRA activated.' + expect(flash[:notice]).to eq 'Jira activated.' end end end describe "GET #edit" do before do - get :edit, params: { namespace_id: project.namespace, project_id: project, id: service_id } + get :edit, params: { namespace_id: project.namespace, project_id: project, id: 'jira' } end context 'with approved services' do - let(:service_id) { 'jira' } - - it 'renders edit page' do - expect(response).to be_success - end - end - - context 'with a deprecated service' do - let(:service_id) { 'kubernetes' } - it 'renders edit page' do - expect(response).to be_success + expect(response).to be_successful end end end diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb index b34053fc993..7f67f67e775 100644 --- a/spec/controllers/projects/settings/repository_controller_spec.rb +++ b/spec/controllers/projects/settings/repository_controller_spec.rb @@ -32,4 +32,24 @@ describe Projects::Settings::RepositoryController do expect(RepositoryCleanupWorker).to have_received(:perform_async).once end end + + describe 'POST create_deploy_token' do + let(:deploy_token_params) do + { + name: 'deployer_token', + expires_at: 1.month.from_now.to_date.to_s, + username: 'deployer', + read_repository: '1' + } + end + + subject(:create_deploy_token) { post :create_deploy_token, params: { namespace_id: project.namespace, project_id: project, deploy_token: deploy_token_params } } + + it 'creates deploy token' do + expect { create_deploy_token }.to change { DeployToken.active.count }.by(1) + + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:show) + end + end end diff --git a/spec/controllers/projects/starrers_controller_spec.rb b/spec/controllers/projects/starrers_controller_spec.rb new file mode 100644 index 00000000000..5774ff7c576 --- /dev/null +++ b/spec/controllers/projects/starrers_controller_spec.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::StarrersController do + let(:user_1) { create(:user, name: 'John') } + let(:user_2) { create(:user, name: 'Michael') } + let(:private_user) { create(:user, name: 'Michael Douglas', private_profile: true) } + let(:admin) { create(:user, admin: true) } + let(:project) { create(:project, :public) } + + before do + user_1.toggle_star(project) + user_2.toggle_star(project) + private_user.toggle_star(project) + end + + describe 'GET index' do + def get_starrers(search: nil) + get :index, params: { namespace_id: project.namespace, project_id: project, search: search } + end + + def user_ids + assigns[:starrers].map { |s| s['user_id'] } + end + + shared_examples 'starrers counts' do + it 'starrers counts are correct' do + expect(assigns[:total_count]).to eq(3) + expect(assigns[:public_count]).to eq(2) + expect(assigns[:private_count]).to eq(1) + end + end + + context 'N+1 queries' do + render_views + + it 'avoids N+1s loading users', :request_store do + get_starrers + + control_count = ActiveRecord::QueryRecorder.new { get_starrers }.count + + create_list(:user, 5).each { |user| user.toggle_star(project) } + + expect { get_starrers }.not_to exceed_query_limit(control_count) + end + end + + context 'when project is public' do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context 'when no user is logged in' do + context 'with no searching' do + before do + get_starrers + end + + it 'only users with public profiles are visible' do + expect(user_ids).to contain_exactly(user_1.id, user_2.id) + end + + include_examples 'starrers counts' + end + + context 'when searching by user' do + before do + get_starrers(search: 'Michael') + end + + it 'only users with public profiles are visible' do + expect(user_ids).to contain_exactly(user_2.id) + end + + include_examples 'starrers counts' + end + end + + context 'when public user is logged in' do + before do + sign_in(user_1) + end + + context 'with no searching' do + before do + get_starrers + end + + it 'their star is also visible' do + expect(user_ids).to contain_exactly(user_1.id, user_2.id) + end + + include_examples 'starrers counts' + end + + context 'when searching by user' do + before do + get_starrers(search: 'Michael') + end + + it 'only users with public profiles are visible' do + expect(user_ids).to contain_exactly(user_2.id) + end + + include_examples 'starrers counts' + end + end + + context 'when private user is logged in' do + before do + sign_in(private_user) + end + + context 'with no searching' do + before do + get_starrers + end + + it 'their star is also visible' do + expect(user_ids).to contain_exactly(user_1.id, user_2.id, private_user.id) + end + + include_examples 'starrers counts' + end + + context 'when searching by user' do + before do + get_starrers(search: 'Michael') + end + + it 'only users with public profiles are visible' do + expect(user_ids).to contain_exactly(user_2.id, private_user.id) + end + + include_examples 'starrers counts' + end + end + + context 'when admin is logged in' do + before do + sign_in(admin) + end + + context 'with no searching' do + before do + get_starrers + end + + it 'all users are visible' do + expect(user_ids).to include(user_1.id, user_2.id, private_user.id) + end + + include_examples 'starrers counts' + end + + context 'when searching by user' do + before do + get_starrers(search: 'Michael') + end + + it 'public and private starrers are visible' do + expect(user_ids).to contain_exactly(user_2.id, private_user.id) + end + + include_examples 'starrers counts' + end + end + end + + context 'when project is private' do + before do + project.update(visibility_level: Project::PRIVATE) + end + + it 'starrers are not visible for non logged in users' do + get_starrers + + expect(assigns[:starrers]).to be_blank + end + + context 'when user is logged in' do + before do + sign_in(project.creator) + get_starrers + end + + it 'only users with public profiles are visible' do + expect(user_ids).to contain_exactly(user_1.id, user_2.id) + end + + include_examples 'starrers counts' + end + end + end +end diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index bebf17728c0..d5ef2b0e114 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -3,49 +3,100 @@ require 'spec_helper' describe Projects::TemplatesController do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, :private) } let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:file_path_1) { '.gitlab/issue_templates/bug.md' } - let(:body) { JSON.parse(response.body) } + let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' } + let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' } + let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') } + let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') } - before do - project.add_developer(user) - sign_in(user) - end + describe '#show' do + shared_examples 'renders issue templates as json' do + it do + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json) - before do - project.add_user(user, Gitlab::Access::MAINTAINER) - project.repository.create_file(user, file_path_1, 'something valid', - message: 'test 3', branch_name: 'master') - end + expect(response.status).to eq(200) + expect(json_response['name']).to eq('issue_template') + expect(json_response['content']).to eq('issue content') + end + end - describe '#show' do - it 'renders template name and content as json' do - get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json) + shared_examples 'renders merge request templates as json' do + it do + get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json) + + expect(response.status).to eq(200) + expect(json_response['name']).to eq('merge_request_template') + expect(json_response['content']).to eq('merge request content') + end + end + + shared_examples 'renders 404 when requesting an issue template' do + it do + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json) + + expect(response.status).to eq(404) + end + end + + shared_examples 'renders 404 when requesting a merge request template' do + it do + get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json) - expect(response.status).to eq(200) - expect(body["name"]).to eq("bug") - expect(body["content"]).to eq("something valid") + expect(response.status).to eq(404) + end end - it 'renders 404 when unauthorized' do - sign_in(user2) - get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json) + shared_examples 'renders 404 when params are invalid' do + it 'does not route when the template type is invalid' do + expect do + get(:show, params: { namespace_id: project.namespace, template_type: 'invalid_type', key: 'issue_template', project_id: project }, format: :json) + end.to raise_error(ActionController::UrlGenerationError) + end + + it 'renders 404 when the format type is invalid' do + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :html) + + expect(response.status).to eq(404) + end + + it 'renders 404 when the key is unknown' do + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'unknown_template', project_id: project }, format: :json) - expect(response.status).to eq(404) + expect(response.status).to eq(404) + end end - it 'renders 404 when template type is not found' do - sign_in(user) - get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) + context 'when the user is not a member of the project' do + before do + sign_in(user) + end - expect(response.status).to eq(404) + include_examples 'renders 404 when requesting an issue template' + include_examples 'renders 404 when requesting a merge request template' + include_examples 'renders 404 when params are invalid' end - it 'renders 404 without errors' do - sign_in(user) - expect { get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) }.not_to raise_error + context 'when user is a member of the project' do + before do + project.add_developer(user) + sign_in(user) + end + + include_examples 'renders issue templates as json' + include_examples 'renders merge request templates as json' + include_examples 'renders 404 when params are invalid' + end + + context 'when user is a guest of the project' do + before do + project.add_guest(user) + sign_in(user) + end + + include_examples 'renders issue templates as json' + include_examples 'renders 404 when requesting a merge request template' + include_examples 'renders 404 when params are invalid' end end end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb index 776c1270977..661ed9840b1 100644 --- a/spec/controllers/projects/uploads_controller_spec.rb +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -10,6 +10,11 @@ describe Projects::UploadsController do { namespace_id: model.namespace.to_param, project_id: model } end + let(:other_model) { create(:project, :public) } + let(:other_params) do + { namespace_id: other_model.namespace.to_param, project_id: other_model } + end + it_behaves_like 'handle uploads' context 'when the URL the old style, without /-/system' do diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb index a2a09e2580f..21e106660d0 100644 --- a/spec/controllers/projects/variables_controller_spec.rb +++ b/spec/controllers/projects/variables_controller_spec.rb @@ -36,5 +36,70 @@ describe Projects::VariablesController do end include_examples 'PATCH #update updates variables' + + context 'with environment scope' do + let!(:variable) { create(:ci_variable, project: project, environment_scope: 'custom_scope') } + + let(:variable_attributes) do + { id: variable.id, + key: variable.key, + secret_value: variable.value, + protected: variable.protected?.to_s, + environment_scope: variable.environment_scope } + end + let(:new_variable_attributes) do + { key: 'new_key', + secret_value: 'dummy_value', + protected: 'false', + environment_scope: 'new_scope' } + end + + context 'with same key and different environment scope' do + let(:variables_attributes) do + [ + variable_attributes, + new_variable_attributes.merge(key: variable.key) + ] + end + + it 'does not update the existing variable' do + expect { subject }.not_to change { variable.reload.value } + end + + it 'creates the new variable' do + expect { subject }.to change { owner.variables.count }.by(1) + end + + it 'returns a successful response including all variables' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('variables') + end + end + + context 'with same key and same environment scope' do + let(:variables_attributes) do + [ + variable_attributes, + new_variable_attributes.merge(key: variable.key, environment_scope: variable.environment_scope) + ] + end + + it 'does not update the existing variable' do + expect { subject }.not_to change { variable.reload.value } + end + + it 'does not create the new variable' do + expect { subject }.not_to change { owner.variables.count } + end + + it 'returns a bad request response' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end end end diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb index f2e0b5e5c1d..fbca1d5740f 100644 --- a/spec/controllers/projects/wikis_controller_spec.rb +++ b/spec/controllers/projects/wikis_controller_spec.rb @@ -31,6 +31,47 @@ describe Projects::WikisController do end end + describe 'GET #history' do + before do + allow(controller) + .to receive(:can?) + .with(any_args) + .and_call_original + + # The :create_wiki permission is irrelevant to reading history. + expect(controller) + .not_to receive(:can?) + .with(anything, :create_wiki, any_args) + + allow(controller) + .to receive(:can?) + .with(anything, :read_wiki, any_args) + .and_return(allow_read_wiki) + end + + shared_examples 'fetching history' do |expected_status| + before do + get :history, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } + end + + it "returns status #{expected_status}" do + expect(response).to have_http_status(expected_status) + end + end + + it_behaves_like 'fetching history', :ok do + let(:allow_read_wiki) { true } + + it 'assigns @page_versions' do + expect(assigns(:page_versions)).to be_present + end + end + + it_behaves_like 'fetching history', :not_found do + let(:allow_read_wiki) { false } + end + end + describe 'GET #show' do render_views @@ -103,7 +144,7 @@ describe Projects::WikisController do it 'renders json in a correct format' do post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' } - expect(JSON.parse(response.body).keys).to match_array(%w(body references)) + expect(json_response.keys).to match_array(%w(body references)) end end |
