diff options
author | Douwe Maan <douwe@gitlab.com> | 2018-11-06 15:43:24 +0000 |
---|---|---|
committer | Douwe Maan <douwe@gitlab.com> | 2018-11-06 15:43:24 +0000 |
commit | 1208d55206128266690f46f0165df0fc10c24941 (patch) | |
tree | 93fbdde5a5db6cdd8f79f2806707dab093985aa2 /spec | |
parent | d171ff60168cd55b6d7b9ee920269f44a26e577e (diff) | |
parent | d0c58a97c8a053c0beec7c13c1c6ec5042120ef1 (diff) | |
download | gitlab-ce-1208d55206128266690f46f0165df0fc10c24941.tar.gz |
Merge branch 'master' into 'refactor-snippets-finder'refactor-snippets-finder
# Conflicts:
# spec/models/project_spec.rb
Diffstat (limited to 'spec')
115 files changed, 3118 insertions, 953 deletions
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb index f7a4a4192d6..99429c93b82 100644 --- a/spec/controllers/groups/boards_controller_spec.rb +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -32,12 +32,13 @@ describe Groups::BoardsController do end it 'renders template if visited board is not found' do - visited = double + temporary_board = create(:board, group: group) + visited = create(:board_group_recent_visit, group: temporary_board.group, board: temporary_board, user: user) + temporary_board.delete - allow(visited).to receive(:board_id).and_return(12) allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited) - list_boards format: :html + list_boards expect(response).to render_template :index expect(response.content_type).to eq 'text/html' diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 667eaa5e34f..8d503f6ad32 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -38,9 +38,10 @@ describe Projects::BoardsController do end it 'renders template if visited board is not found' do - visited = double + temporary_board = create(:board, project: project) + visited = create(:board_project_recent_visit, project: temporary_board.project, board: temporary_board, user: user) + temporary_board.delete - allow(visited).to receive(:board_id).and_return(12) allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited) list_boards diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb index 9e17e392d3d..8106453a775 100644 --- a/spec/controllers/projects/clusters/applications_controller_spec.rb +++ b/spec/controllers/projects/clusters/applications_controller_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::Clusters::ApplicationsController do diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 9201332c5c8..04aece26590 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' describe Projects::ClustersController do include AccessMatchersForController include GoogleApi::CloudPlatformHelpers + include KubernetesHelpers set(:project) { create(:project) } @@ -218,9 +221,9 @@ describe Projects::ClustersController do describe 'security' do before do allow_any_instance_of(described_class) - .to receive(:token_in_session).and_return('token') + .to receive(:token_in_session).and_return('token') allow_any_instance_of(described_class) - .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) + .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) allow_any_instance_of(GoogleApi::CloudPlatform::Client) .to receive(:projects_zones_clusters_create) do OpenStruct.new( @@ -307,6 +310,11 @@ describe Projects::ClustersController do end describe 'security' do + before do + allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') + end + it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:owner).of(project) } it { expect { go }.to be_allowed_for(:maintainer).of(project) } @@ -318,14 +326,15 @@ describe Projects::ClustersController do end end - describe 'GET status' do + describe 'GET cluster_status' do let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } def go - get :status, namespace_id: project.namespace, - project_id: project, - id: cluster, - format: :json + get :cluster_status, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: cluster, + format: :json end describe 'functionality' do @@ -359,9 +368,10 @@ describe Projects::ClustersController do let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } def go - get :show, namespace_id: project.namespace, - project_id: project, - id: cluster + get :show, + namespace_id: project.namespace, + project_id: project, + id: cluster end describe 'functionality' do @@ -401,13 +411,18 @@ describe Projects::ClustersController do end def go(format: :html) - put :update, params.merge(namespace_id: project.namespace, - project_id: project, + put :update, params.merge(namespace_id: project.namespace.to_param, + project_id: project.to_param, id: cluster, format: format ) end + before do + allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') + end + context 'when cluster is provided by GCP' do it "updates and redirects back to show page" do go @@ -530,9 +545,10 @@ describe Projects::ClustersController do let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } def go - delete :destroy, namespace_id: project.namespace, - project_id: project, - id: cluster + delete :destroy, + namespace_id: project.namespace, + project_id: project, + id: cluster end describe 'functionality' do @@ -591,4 +607,10 @@ describe Projects::ClustersController do it { expect { go }.to be_denied_for(:external) } end end + + context 'no project_id param' do + it 'does not respond to any action without project_id param' do + expect { get :index }.to raise_error(ActionController::UrlGenerationError) + end + end end diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 9e149bc4c3c..e34fdee62d6 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -356,6 +356,7 @@ describe Projects::CommitController do 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(response).to include_pagination_headers end end end diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index d1c960e895d..5b7da81b6a1 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -15,9 +15,9 @@ describe Projects::DeploymentsController do describe 'GET #index' do it 'returns list of deployments from last 8 hours' do - create(:deployment, environment: environment, created_at: 9.hours.ago) - create(:deployment, environment: environment, created_at: 7.hours.ago) - create(:deployment, environment: environment) + create(:deployment, :success, environment: environment, created_at: 9.hours.ago) + create(:deployment, :success, environment: environment, created_at: 7.hours.ago) + create(:deployment, :success, environment: environment) get :index, deployment_params(after: 8.hours.ago) @@ -27,7 +27,7 @@ describe Projects::DeploymentsController do end it 'returns a list with deployments information' do - create(:deployment, environment: environment) + create(:deployment, :success, environment: environment) get :index, deployment_params @@ -37,7 +37,7 @@ describe Projects::DeploymentsController do end describe 'GET #metrics' do - let(:deployment) { create(:deployment, project: project, environment: environment) } + let(:deployment) { create(:deployment, :success, project: project, environment: environment) } before do allow(controller).to receive(:deployment).and_return(deployment) @@ -110,7 +110,7 @@ describe Projects::DeploymentsController do end describe 'GET #additional_metrics' do - let(:deployment) { create(:deployment, project: project, environment: environment) } + let(:deployment) { create(:deployment, :success, project: project, environment: environment) } before do allow(controller).to receive(:deployment).and_return(deployment) diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 8eb01145ed5..da3d658d061 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -231,7 +231,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do context 'with deployment' do let(:merge_request) { create(:merge_request, source_project: project) } let(:environment) { create(:environment, project: project, name: 'staging', state: :available) } - let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } + let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) } it 'exposes the deployment information' do expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7463586621c..e62523c65c9 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -563,6 +563,7 @@ describe Projects::MergeRequestsController do 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 end end @@ -754,7 +755,7 @@ describe Projects::MergeRequestsController do let(:environment) { create(:environment, project: forked) } let(:pipeline) { create(:ci_pipeline, sha: sha, project: forked) } let(:build) { create(:ci_build, pipeline: pipeline) } - let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: 'master', deployable: build) } + let!(:deployment) { create(:deployment, :succeed, environment: environment, sha: sha, ref: 'master', deployable: build) } let(:merge_request) do create(:merge_request, source_project: forked, target_project: project, target_branch: 'master', head_pipeline: pipeline) @@ -779,7 +780,7 @@ describe Projects::MergeRequestsController do let(:merge_commit_sha) { project.repository.merge(user, forked.commit.id, merge_request, "merged in test") } let(:post_merge_pipeline) { create(:ci_pipeline, sha: merge_commit_sha, project: project) } let(:post_merge_build) { create(:ci_build, pipeline: post_merge_pipeline) } - let!(:source_deployment) { create(:deployment, environment: source_environment, sha: merge_commit_sha, ref: 'master', deployable: post_merge_build) } + let!(:source_deployment) { create(:deployment, :succeed, environment: source_environment, sha: merge_commit_sha, ref: 'master', deployable: post_merge_build) } before do merge_request.update!(merge_commit_sha: merge_commit_sha) diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 85ba7d4097d..90754319f05 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -27,6 +27,12 @@ FactoryBot.define do pipeline factory: :ci_pipeline + trait :degenerated do + commands nil + options nil + yaml_variables nil + end + trait :started do started_at 'Di 29. Okt 09:51:28 CET 2013' end @@ -94,6 +100,30 @@ FactoryBot.define do url: 'http://staging.example.com/$CI_JOB_NAME' } end + trait :deploy_to_production do + environment 'production' + + options environment: { name: 'production', + url: 'http://prd.example.com/$CI_JOB_NAME' } + end + + trait :start_review_app do + environment 'review/$CI_COMMIT_REF_NAME' + + options environment: { name: 'review/$CI_COMMIT_REF_NAME', + url: 'http://staging.example.com/$CI_JOB_NAME', + on_stop: 'stop_review_app' } + end + + trait :stop_review_app do + name 'stop_review_app' + environment 'review/$CI_COMMIT_REF_NAME' + + options environment: { name: 'review/$CI_COMMIT_REF_NAME', + url: 'http://staging.example.com/$CI_JOB_NAME', + action: 'stop' } + end + trait :allowed_to_fail do allow_failure true end diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb index 6fdada75a3d..3f10f0ecc74 100644 --- a/spec/factories/clusters/kubernetes_namespaces.rb +++ b/spec/factories/clusters/kubernetes_namespaces.rb @@ -2,8 +2,18 @@ FactoryBot.define do factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do - cluster - project - cluster_project + association :cluster, :project, :provided_by_gcp + namespace { |n| "environment#{n}" } + + after(:build) do |kubernetes_namespace| + cluster_project = kubernetes_namespace.cluster.cluster_project + + kubernetes_namespace.project = cluster_project.project + kubernetes_namespace.cluster_project = cluster_project + end + + trait :with_token do + service_account_token { Faker::Lorem.characters(10) } + end end end diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index 90d6a338479..011c98599a3 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -21,5 +21,31 @@ FactoryBot.define do sha { TestEnv::BRANCH_SHA['pages-deploy'] } ref 'pages-deploy' end + + trait :running do + status :running + end + + trait :success do + status :success + finished_at { Time.now } + end + + trait :failed do + status :failed + finished_at { Time.now } + end + + trait :canceled do + status :canceled + finished_at { Time.now } + end + + # This trait hooks the state maechine's events + trait :succeed do + after(:create) do |deployment, evaluator| + deployment.succeed! + end + end end end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index b5db57d5148..9d9e3d693b8 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -22,6 +22,7 @@ FactoryBot.define do pipeline: pipeline) deployment = create(:deployment, + :success, environment: environment, project: environment.project, deployable: deployable, diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index aa3ca8923ff..a1f93bd3fbd 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -153,7 +153,7 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity log' do - expect(find('.tab-pane#activity .content_list .event-note')).to have_content issue_title + expect(find('.tab-pane#activity .content_list .event-target-title')).to have_content issue_title end end end diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb index 498775acff3..16919fe63ad 100644 --- a/spec/features/dashboard/project_member_activity_index_spec.rb +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -14,14 +14,15 @@ describe 'Project member activity', :js do wait_for_requests end - subject { page.find(".event-title").text } - context 'when a user joins the project' do before do visit_activities_and_wait_with_event(Event::JOINED) end - it { is_expected.to eq("#{user.name} joined project") } + it "presents the correct message" do + expect(page.find('.event-user-info').text).to eq("#{user.name} #{user.to_reference}") + expect(page.find('.event-title').text).to eq("joined project") + end end context 'when a user leaves the project' do @@ -29,7 +30,10 @@ describe 'Project member activity', :js do visit_activities_and_wait_with_event(Event::LEFT) end - it { is_expected.to eq("#{user.name} left project") } + it "presents the correct message" do + expect(page.find('.event-user-info').text).to eq("#{user.name} #{user.to_reference}") + expect(page.find('.event-title').text).to eq("left project") + end end context 'when a users membership expires for the project' do @@ -38,8 +42,8 @@ describe 'Project member activity', :js do end it "presents the correct message" do - message = "#{user.name} removed due to membership expiration from project" - is_expected.to eq(message) + expect(page.find('.event-user-info').text).to eq("#{user.name} #{user.to_reference}") + expect(page.find('.event-title').text).to eq("removed due to membership expiration from project") end end end diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb new file mode 100644 index 00000000000..9f597efa7b7 --- /dev/null +++ b/spec/features/groups/board_sidebar_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Group Issue Boards', :js do + include BoardHelpers + + let(:group) { create(:group) } + let(:user) { create(:group_member, user: create(:user), group: group ).user } + let!(:project_1) { create(:project, :public, group: group) } + let!(:project_2) { create(:project, :public, group: group) } + let!(:project_1_label) { create(:label, project: project_1, name: 'Development 1') } + let!(:project_2_label) { create(:label, project: project_2, name: 'Development 2') } + let!(:group_label) { create(:group_label, title: 'Bug', description: 'Fusce consequat', group: group) } + let!(:issue_1) { create(:labeled_issue, project: project_1, relative_position: 1) } + let!(:issue_2) { create(:labeled_issue, project: project_2, relative_position: 2) } + let(:board) { create(:board, group: group) } + let!(:list) { create(:list, board: board, label: project_1_label, position: 0) } + let(:card) { find('.board:nth-child(1)').first('.board-card') } + + before do + sign_in(user) + + visit group_board_path(group, board) + wait_for_requests + end + + context 'labels' do + it 'only shows valid labels for the issue project and group' do + click_card(card) + + page.within('.labels') do + click_link 'Edit' + + wait_for_requests + + page.within('.selectbox') do + expect(page).to have_content(project_1_label.title) + expect(page).to have_content(group_label.title) + expect(page).not_to have_content(project_2_label.title) + end + end + end + end +end diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb index a298ead43db..0e439c8cb2d 100644 --- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb +++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb @@ -11,7 +11,7 @@ describe 'Merge request > User sees deployment widget', :js do let(:sha) { project.commit(ref).id } let(:pipeline) { create(:ci_pipeline_without_jobs, sha: sha, project: project, ref: ref) } let(:build) { create(:ci_build, :success, pipeline: pipeline) } - let!(:deployment) { create(:deployment, environment: environment, sha: sha, ref: ref, deployable: build) } + let!(:deployment) { create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build) } let!(:manual) { } before do @@ -38,7 +38,7 @@ describe 'Merge request > User sees deployment widget', :js do end it 'does start build when stop button clicked' do - accept_confirm { click_button('Stop environment') } + accept_confirm { find('.js-stop-env').click } expect(page).to have_content('close_app') end @@ -47,7 +47,7 @@ describe 'Merge request > User sees deployment widget', :js do let(:role) { :reporter } it 'does not show stop button' do - expect(page).not_to have_button('Stop environment') + expect(page).not_to have_selector('.js-stop-env') end end end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 0c610edd6d1..d907ed4198c 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -45,7 +45,8 @@ describe 'Merge request > User sees merge widget', :js do let(:build) { create(:ci_build, :success, pipeline: pipeline) } let!(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :succeed, + environment: environment, ref: merge_request.source_branch, deployable: build, sha: sha) @@ -179,7 +180,7 @@ describe 'Merge request > User sees merge widget', :js do # Wait for the `ci_status` and `merge_check` requests wait_for_requests - expect(page).to have_text(%r{Could not retrieve the pipeline status\. For troubleshooting steps, read the <a href=\".+\">documentation\.</a>}) + expect(page).to have_text("Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.") end end diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index 41f447fba95..8faddee4daa 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -41,8 +41,7 @@ describe 'Merge request > User sees pipelines', :js do visit project_merge_request_path(project, merge_request) wait_for_requests - expect(page.find('.ci-widget')).to have_text( - %r{Could not retrieve the pipeline status\. For troubleshooting steps, read the <a href=\".+\">documentation\.</a>}) + expect(page.find('.ci-widget')).to have_text("Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.") end end diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb index 8fd057d587c..5de0c381cdf 100644 --- a/spec/features/milestones/user_creates_milestone_spec.rb +++ b/spec/features/milestones/user_creates_milestone_spec.rb @@ -24,6 +24,6 @@ describe "User creates milestone", :js do visit(activity_project_path(project)) - expect(page).to have_content("#{user.name} opened milestone") + expect(page).to have_content("#{user.name} #{user.to_reference} opened milestone") end end diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb index a8c296b4cd2..f68ed1cde07 100644 --- a/spec/features/milestones/user_deletes_milestone_spec.rb +++ b/spec/features/milestones/user_deletes_milestone_spec.rb @@ -23,7 +23,7 @@ describe "User deletes milestone", :js do visit(activity_project_path(project)) - expect(page).to have_content("#{user.name} destroyed milestone") + expect(page).to have_content("#{user.name} #{user.to_reference} destroyed milestone") end end diff --git a/spec/features/projects/activity/user_sees_activity_spec.rb b/spec/features/projects/activity/user_sees_activity_spec.rb index ebaa137772d..bb4b2abc3c7 100644 --- a/spec/features/projects/activity/user_sees_activity_spec.rb +++ b/spec/features/projects/activity/user_sees_activity_spec.rb @@ -19,13 +19,13 @@ describe 'Projects > Activity > User sees activity' do it 'shows the last push in the activity page', :js do visit activity_project_path(project) - expect(page).to have_content "#{user.name} pushed new branch fix" + expect(page).to have_content "#{user.name} #{user.to_reference} pushed new branch fix" end it 'allows to filter event with the "event_filter=issue" URL param', :js do visit activity_project_path(project, event_filter: 'issue') - expect(page).not_to have_content "#{user.name} pushed new branch fix" - expect(page).to have_content "#{user.name} opened issue #{issue.to_reference}" + expect(page).not_to have_content "#{user.name} #{user.to_reference} pushed new branch fix" + expect(page).to have_content "#{user.name} #{user.to_reference} opened issue #{issue.to_reference}" end end diff --git a/spec/features/projects/activity/user_sees_private_activity_spec.rb b/spec/features/projects/activity/user_sees_private_activity_spec.rb index d7dc0a6712a..61ec2ce9d29 100644 --- a/spec/features/projects/activity/user_sees_private_activity_spec.rb +++ b/spec/features/projects/activity/user_sees_private_activity_spec.rb @@ -5,7 +5,7 @@ describe 'Project > Activity > User sees private activity', :js do let(:author) { create(:user) } let(:user) { create(:user) } let(:issue) { create(:issue, :confidential, project: project, author: author) } - let(:message) { "#{author.name} opened issue #{issue.to_reference}" } + let(:message) { "#{author.name} #{author.to_reference} opened issue #{issue.to_reference}" } before do project.add_developer(author) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 8b92b9fc869..3d17eb3a73a 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -130,6 +130,7 @@ describe 'Gcp Cluster', :js do context 'when user changes cluster parameters' do before do + allow(ClusterPlatformConfigureWorker).to receive(:perform_async) fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' page.within('#js-cluster-details') { click_button 'Save changes' } end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 9ae1dba60b5..250c964cc32 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -9,7 +9,9 @@ describe 'User Cluster', :js do before do project.add_maintainer(user) gitlab_sign_in(user) + allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } + allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) end context 'when user does not have a cluster and visits cluster index page' do diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 70e0879dd81..056f4ee2e22 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -33,7 +33,7 @@ describe 'Environment' do context 'with deployments' do context 'when there is no related deployable' do let(:deployment) do - create(:deployment, environment: environment, deployable: nil) + create(:deployment, :success, environment: environment, deployable: nil) end it 'does show deployment SHA' do @@ -48,15 +48,26 @@ describe 'Environment' do let(:build) { create(:ci_build, pipeline: pipeline) } let(:deployment) do - create(:deployment, environment: environment, deployable: build) + create(:deployment, :success, environment: environment, deployable: build) end it 'does show build name' do expect(page).to have_link("#{build.name} (##{build.id})") - expect(page).to have_link('Re-deploy') + expect(page).not_to have_link('Re-deploy') expect(page).not_to have_terminal_button end + context 'when user has ability to re-deploy' do + let(:permissions) do + create(:protected_branch, :developers_can_merge, + name: build.ref, project: project) + end + + it 'does show re-deploy' do + expect(page).to have_link('Re-deploy') + end + end + context 'with manual action' do let(:action) do create(:ci_build, :manual, pipeline: pipeline, @@ -97,7 +108,7 @@ describe 'Environment' do context 'with external_url' do let(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } let(:build) { create(:ci_build, pipeline: pipeline) } - let(:deployment) { create(:deployment, environment: environment, deployable: build) } + let(:deployment) { create(:deployment, :success, environment: environment, deployable: build) } it 'does show an external link button' do expect(page).to have_link(nil, href: environment.external_url) @@ -158,7 +169,8 @@ describe 'Environment' do end let(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :success, + environment: environment, deployable: build, on_stop: 'close_app') end diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 917ba495f01..d0ddf69d574 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -132,7 +132,8 @@ describe 'Environments page', :js do let(:project) { create(:project, :repository) } let!(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :success, + environment: environment, sha: project.commit.id) end @@ -152,7 +153,8 @@ describe 'Environments page', :js do end let!(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :success, + environment: environment, deployable: build, sha: project.commit.id) end @@ -162,7 +164,7 @@ describe 'Environments page', :js do end it 'shows a play button' do - find('.js-dropdown-play-icon-container').click + find('.js-environment-actions-dropdown').click expect(page).to have_content(action.name.humanize) end @@ -170,7 +172,7 @@ describe 'Environments page', :js do it 'allows to play a manual action', :js do expect(action).to be_manual - find('.js-dropdown-play-icon-container').click + find('.js-environment-actions-dropdown').click expect(page).to have_content(action.name.humanize) expect { find('.js-manual-action-link').click } @@ -196,7 +198,7 @@ describe 'Environments page', :js do context 'with external_url' do let(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } let(:build) { create(:ci_build, pipeline: pipeline) } - let(:deployment) { create(:deployment, environment: environment, deployable: build) } + let(:deployment) { create(:deployment, :success, environment: environment, deployable: build) } it 'shows an external link button' do expect(page).to have_link(nil, href: environment.external_url) @@ -209,7 +211,8 @@ describe 'Environments page', :js do end let(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :success, + environment: environment, deployable: build, on_stop: 'close_app') end @@ -260,6 +263,70 @@ describe 'Environments page', :js do end end end + + context 'when there is a delayed job' do + let!(:pipeline) { create(:ci_pipeline, project: project) } + let!(:build) { create(:ci_build, pipeline: pipeline) } + + let!(:delayed_job) do + create(:ci_build, :scheduled, + pipeline: pipeline, + name: 'delayed job', + stage: 'test', + commands: 'test') + end + + let!(:deployment) do + create(:deployment, + :success, + environment: environment, + deployable: build, + sha: project.commit.id) + end + + before do + visit_environments(project) + end + + it 'has a dropdown for actionable jobs' do + expect(page).to have_selector('.dropdown-new.btn.btn-default .ic-play') + end + + it "has link to the delayed job's action" do + find('.js-environment-actions-dropdown').click + + expect(page).to have_button('Delayed job') + expect(page).to have_content(/\d{2}:\d{2}:\d{2}/) + end + + context 'when delayed job is expired already' do + let!(:delayed_job) do + create(:ci_build, :expired_scheduled, + pipeline: pipeline, + name: 'delayed job', + stage: 'test', + commands: 'test') + end + + it "shows 00:00:00 as the remaining time" do + find('.js-environment-actions-dropdown').click + + expect(page).to have_content("00:00:00") + end + end + + context 'when user played a delayed job immediately' do + before do + find('.js-environment-actions-dropdown').click + page.accept_confirm { click_button('Delayed job') } + wait_for_requests + end + + it 'enqueues the delayed job', :js do + expect(delayed_job.reload).to be_pending + end + end + end end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 5cb3f7c732f..cbb935abd53 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -396,8 +396,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end context 'job is successful and has deployment' do - let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline) } - let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) } + let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline, deployment: deployment) } + let(:deployment) { create(:deployment, :success, environment: environment, project: environment.project) } it 'shows a link for the job' do expect(page).to have_link environment.name @@ -419,7 +419,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end context 'deployment still not finished' do - let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } + let(:build) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) } it 'shows a link to latest deployment' do expect(page).to have_link environment.name @@ -456,6 +456,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do describe 'environment info in job view', :js do before do + allow_any_instance_of(Ci::Build).to receive(:create_deployment) + visit project_job_path(project, job) wait_for_requests end @@ -464,8 +466,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do let(:job) { create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) } let(:second_build) { create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) } let(:environment) { create(:environment, name: 'staging', project: project) } - let!(:first_deployment) { create(:deployment, environment: environment, deployable: job) } - let!(:second_deployment) { create(:deployment, environment: environment, deployable: second_build) } + let!(:first_deployment) { create(:deployment, :success, environment: environment, deployable: job) } + let!(:second_deployment) { create(:deployment, :success, environment: environment, deployable: second_build) } it 'shows deployment message' do expected_text = 'This job is an out-of-date deployment ' \ @@ -505,7 +507,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end context 'when it has deployment' do - let!(:deployment) { create(:deployment, environment: environment) } + let!(:deployment) { create(:deployment, :success, environment: environment) } it 'shows that deployment will be overwritten' do expected_text = 'This job is creating a deployment to staging' diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index cd6c37bf54d..049bbca958f 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -388,54 +388,83 @@ describe 'Pipeline', :js do let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) } let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) } + subject { visit pipeline_failures_page } + context 'with failed build' do before do failed_build.trace.set('4 examples, 1 failure') - - visit pipeline_failures_page end it 'shows jobs tab pane as active' do + subject + expect(page).to have_content('Failed Jobs') expect(page).to have_css('#js-tab-failures.active') end it 'lists failed builds' do + subject + expect(page).to have_content(failed_build.name) expect(page).to have_content(failed_build.stage) end it 'shows build failure logs' do + subject + expect(page).to have_content('4 examples, 1 failure') end it 'shows the failure reason' do + subject + expect(page).to have_content('There is an unknown failure, please try again') end - it 'shows retry button for failed build' do - page.within(find('.build-failures', match: :first)) do - expect(page).to have_link('Retry') + context 'when user does not have permission to retry build' do + it 'shows retry button for failed build' do + subject + + page.within(find('.build-failures', match: :first)) do + expect(page).not_to have_link('Retry') + end end end - end - context 'when missing build logs' do - before do - visit pipeline_failures_page + context 'when user does have permission to retry build' do + before do + create(:protected_branch, :developers_can_merge, + name: pipeline.ref, project: project) + end + + it 'shows retry button for failed build' do + subject + + page.within(find('.build-failures', match: :first)) do + expect(page).to have_link('Retry') + end + end end + end + context 'when missing build logs' do it 'shows jobs tab pane as active' do + subject + expect(page).to have_content('Failed Jobs') expect(page).to have_css('#js-tab-failures.active') end it 'lists failed builds' do + subject + expect(page).to have_content(failed_build.name) expect(page).to have_content(failed_build.stage) end it 'does not show trace' do + subject + expect(page).to have_content('No job trace') end end @@ -448,11 +477,9 @@ describe 'Pipeline', :js do end context 'when accessing failed jobs page' do - before do - visit pipeline_failures_page - end - it 'fails to access the page' do + subject + expect(page).to have_title('Access Denied') end end @@ -461,11 +488,11 @@ describe 'Pipeline', :js do context 'without failures' do before do failed_build.update!(status: :success) - - visit pipeline_failures_page end it 'displays the pipeline graph' do + subject + expect(current_path).to eq(pipeline_path(pipeline)) expect(page).not_to have_content('Failed Jobs') expect(page).to have_selector('.pipeline-visualization') diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb index a48ad94e9fa..7bfcd46713e 100644 --- a/spec/features/projects/view_on_env_spec.rb +++ b/spec/features/projects/view_on_env_spec.rb @@ -44,7 +44,7 @@ describe 'View on environment', :js do context 'and an active deployment' do let(:sha) { project.commit(branch_name).sha } let(:environment) { create(:environment, project: project, name: 'review/feature', external_url: 'http://feature.review.example.com') } - let!(:deployment) { create(:deployment, environment: environment, ref: branch_name, sha: sha) } + let!(:deployment) { create(:deployment, :success, environment: environment, ref: branch_name, sha: sha) } context 'when visiting the diff of a merge request for the branch' do let(:merge_request) { create(:merge_request, :simple, source_project: project, source_branch: branch_name) } diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb index 3cd421f22eb..25835bb4d94 100644 --- a/spec/finders/environments_finder_spec.rb +++ b/spec/finders/environments_finder_spec.rb @@ -12,7 +12,7 @@ describe EnvironmentsFinder do context 'tagged deployment' do before do - create(:deployment, environment: environment, ref: 'v1.1.0', tag: true, sha: project.commit.id) + create(:deployment, :success, environment: environment, ref: 'v1.1.0', tag: true, sha: project.commit.id) end it 'returns environment when with_tags is set' do @@ -33,7 +33,7 @@ describe EnvironmentsFinder do context 'branch deployment' do before do - create(:deployment, environment: environment, ref: 'master', sha: project.commit.id) + create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) end it 'returns environment when ref is set' do @@ -59,7 +59,7 @@ describe EnvironmentsFinder do context 'commit deployment' do before do - create(:deployment, environment: environment, ref: 'master', sha: project.commit.id) + create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id) end it 'returns environment' do @@ -71,7 +71,7 @@ describe EnvironmentsFinder do context 'recently updated' do context 'when last deployment to environment is the most recent one' do before do - create(:deployment, environment: environment, ref: 'feature') + create(:deployment, :success, environment: environment, ref: 'feature') end it 'finds recently updated environment' do @@ -82,8 +82,8 @@ describe EnvironmentsFinder do context 'when last deployment to environment is not the most recent' do before do - create(:deployment, environment: environment, ref: 'feature') - create(:deployment, environment: environment, ref: 'master') + create(:deployment, :success, environment: environment, ref: 'feature') + create(:deployment, :success, environment: environment, ref: 'master') end it 'does not find environment' do @@ -96,8 +96,8 @@ describe EnvironmentsFinder do let(:second_environment) { create(:environment, project: project) } before do - create(:deployment, environment: environment, ref: 'feature') - create(:deployment, environment: second_environment, ref: 'feature') + create(:deployment, :success, environment: environment, ref: 'feature') + create(:deployment, :success, environment: second_environment, ref: 'feature') end it 'finds both environments' do diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index de9974c45e1..b51f1955ac4 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -13,7 +13,7 @@ describe NotesFinder do let!(:comment) { create(:note_on_issue, project: project) } let!(:system_note) { create(:note_on_issue, project: project, system: true) } - it 'filters system notes' do + it 'returns only user notes when using only_comments filter' do finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments]) notes = finder.execute @@ -21,6 +21,14 @@ describe NotesFinder do expect(notes).to match_array(comment) end + it 'returns only system notes when using only_activity filters' do + finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_activity]) + + notes = finder.execute + + expect(notes).to match_array(system_note) + end + it 'gets all notes' do finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity]) diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb index 3f22b3a253d..3e849c9a644 100644 --- a/spec/finders/personal_access_tokens_finder_spec.rb +++ b/spec/finders/personal_access_tokens_finder_spec.rb @@ -92,7 +92,7 @@ describe PersonalAccessTokensFinder do end describe 'with id' do - subject { finder(params).find_by(id: active_personal_access_token.id) } + subject { finder(params).find_by_id(active_personal_access_token.id) } it { is_expected.to eq(active_personal_access_token) } @@ -106,7 +106,7 @@ describe PersonalAccessTokensFinder do end describe 'with token' do - subject { finder(params).find_by(token: active_personal_access_token.token) } + subject { finder(params).find_by_token(active_personal_access_token.token) } it { is_expected.to eq(active_personal_access_token) } @@ -207,7 +207,7 @@ describe PersonalAccessTokensFinder do end describe 'with id' do - subject { finder(params).find_by(id: active_personal_access_token.id) } + subject { finder(params).find_by_id(active_personal_access_token.id) } it { is_expected.to eq(active_personal_access_token) } @@ -221,7 +221,7 @@ describe PersonalAccessTokensFinder do end describe 'with token' do - subject { finder(params).find_by(token: active_personal_access_token.token) } + subject { finder(params).find_by_token(active_personal_access_token.token) } it { is_expected.to eq(active_personal_access_token) } diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json index 44835386cfc..0828f113495 100644 --- a/spec/fixtures/api/schemas/deployment.json +++ b/spec/fixtures/api/schemas/deployment.json @@ -48,6 +48,10 @@ "manual_actions": { "type": "array", "items": { "$ref": "job/job.json" } + }, + "scheduled_actions": { + "type": "array", + "items": { "$ref": "job/job.json" } } }, "additionalProperties": false diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json index 8833825e3fb..4878df43d28 100644 --- a/spec/fixtures/api/schemas/issue.json +++ b/spec/fixtures/api/schemas/issue.json @@ -15,6 +15,7 @@ "relative_position": { "type": "integer" }, "issue_sidebar_endpoint": { "type": "string" }, "toggle_subscription_endpoint": { "type": "string" }, + "assignable_labels_endpoint": { "type": "string" }, "reference_path": { "type": "string" }, "real_path": { "type": "string" }, "project": { diff --git a/spec/fixtures/api/schemas/job/job.json b/spec/fixtures/api/schemas/job/job.json index 734c535ef70..f3d5e9b038a 100644 --- a/spec/fixtures/api/schemas/job/job.json +++ b/spec/fixtures/api/schemas/job/job.json @@ -9,7 +9,8 @@ "playable", "created_at", "updated_at", - "status" + "status", + "archived" ], "properties": { "id": { "type": "integer" }, @@ -27,7 +28,8 @@ "updated_at": { "type": "string" }, "status": { "$ref": "../status/ci_detailed_status.json" }, "callout_message": { "type": "string" }, - "recoverable": { "type": "boolean" } + "recoverable": { "type": "boolean" }, + "archived": { "type": "boolean" } }, "additionalProperties": true } diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index a2cda58e5d2..c04f679bcf0 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -211,4 +211,29 @@ describe LabelsHelper do end end end + + describe 'labels_filter_path' do + let(:group) { create(:group) } + let(:project) { create(:project) } + + it 'links to the dashboard labels page' do + expect(labels_filter_path).to eq(dashboard_labels_path) + end + + it 'links to the group labels page' do + assign(:group, group) + + expect(helper.labels_filter_path).to eq(group_labels_path(group)) + end + + it 'links to the project labels page' do + assign(:project, project) + + expect(helper.labels_filter_path).to eq(project_labels_path(project)) + end + + it 'supports json format' do + expect(labels_filter_path(format: :json)).to eq(dashboard_labels_path(format: :json)) + end + end end diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index ffdf6561a53..ab4566e261b 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe TreeHelper do let(:project) { create(:project, :repository) } let(:repository) { project.repository } - let(:sha) { 'ce369011c189f62c815f5971d096b26759bab0d1' } + let(:sha) { 'c1c67abbaf91f624347bb3ae96eabe3a1b742478' } describe '.render_tree' do before do @@ -32,6 +32,49 @@ describe TreeHelper do end end + describe '.fast_project_blob_path' do + it 'generates the same path as project_blob_path' do + blob_path = repository.tree(sha, 'with space').entries.first.path + fast_path = fast_project_blob_path(project, blob_path) + std_path = project_blob_path(project, blob_path) + + expect(fast_path).to eq(std_path) + end + + it 'generates the same path with encoded file names' do + tree = repository.tree(sha, 'encoding') + blob_path = tree.entries.find { |entry| entry.path == 'encoding/テスト.txt' }.path + fast_path = fast_project_blob_path(project, blob_path) + std_path = project_blob_path(project, blob_path) + + expect(fast_path).to eq(std_path) + end + + it 'respects a configured relative URL' do + allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root') + blob_path = repository.tree(sha, '').entries.first.path + fast_path = fast_project_blob_path(project, blob_path) + + expect(fast_path).to start_with('/gitlab/root') + end + end + + describe '.fast_project_tree_path' do + let(:tree_path) { repository.tree(sha, 'with space').path } + let(:fast_path) { fast_project_tree_path(project, tree_path) } + let(:std_path) { project_tree_path(project, tree_path) } + + it 'generates the same path as project_tree_path' do + expect(fast_path).to eq(std_path) + end + + it 'respects a configured relative URL' do + allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root') + + expect(fast_path).to start_with('/gitlab/root') + end + end + describe 'flatten_tree' do let(:tree) { repository.tree(sha, 'files') } let(:root_path) { 'files' } diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index b797cc44ae7..04c8ab44405 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -72,6 +72,29 @@ describe('Pipelines table in Commits and Merge requests', function() { done(); }, 0); }); + + describe('with pagination', () => { + it('should make an API request when using pagination', done => { + setTimeout(() => { + spyOn(vm, 'updateContent'); + + vm.store.state.pageInfo = { + page: 1, + total: 10, + perPage: 2, + nextPage: 2, + totalPages: 5, + }; + + vm.$nextTick(() => { + vm.$el.querySelector('.js-next-button a').click(); + + expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' }); + done(); + }); + }); + }); + }); }); describe('pipeline badge counts', () => { diff --git a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js index b7b29190c31..093fec97951 100644 --- a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js +++ b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js @@ -1,23 +1,35 @@ import DirtySubmitForm from '~/dirty_submit/dirty_submit_form'; import { setInput, createForm } from './helper'; +function expectToToggleDisableOnDirtyUpdate(submit, input) { + const originalValue = input.value; + + expect(submit.disabled).toBe(true); + + return setInput(input, `${originalValue} changes`) + .then(() => expect(submit.disabled).toBe(false)) + .then(() => setInput(input, originalValue)) + .then(() => expect(submit.disabled).toBe(true)); +} + describe('DirtySubmitForm', () => { it('disables submit until there are changes', done => { const { form, input, submit } = createForm(); - const originalValue = input.value; new DirtySubmitForm(form); // eslint-disable-line no-new - expect(submit.disabled).toBe(true); + return expectToToggleDisableOnDirtyUpdate(submit, input) + .then(done) + .catch(done.fail); + }); + + it('disables submit until there are changes when initializing with a falsy value', done => { + const { form, input, submit } = createForm(); + input.value = ''; + + new DirtySubmitForm(form); // eslint-disable-line no-new - return setInput(input, `${originalValue} changes`) - .then(() => { - expect(submit.disabled).toBe(false); - }) - .then(() => setInput(input, originalValue)) - .then(() => { - expect(submit.disabled).toBe(true); - }) + return expectToToggleDisableOnDirtyUpdate(submit, input) .then(done) .catch(done.fail); }); diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js index 223153d4e31..787df757d32 100644 --- a/spec/javascripts/environments/environment_actions_spec.js +++ b/spec/javascripts/environments/environment_actions_spec.js @@ -1,15 +1,19 @@ import Vue from 'vue'; -import actionsComp from '~/environments/components/environment_actions.vue'; +import eventHub from '~/environments/event_hub'; +import EnvironmentActions from '~/environments/components/environment_actions.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { TEST_HOST } from 'spec/test_constants'; -describe('Actions Component', () => { - let ActionsComponent; - let actionsMock; - let component; +describe('EnvironmentActions Component', () => { + const Component = Vue.extend(EnvironmentActions); + let vm; - beforeEach(() => { - ActionsComponent = Vue.extend(actionsComp); + afterEach(() => { + vm.$destroy(); + }); - actionsMock = [ + describe('manual actions', () => { + const actions = [ { name: 'bar', play_path: 'https://gitlab.com/play', @@ -25,43 +29,89 @@ describe('Actions Component', () => { }, ]; - component = new ActionsComponent({ - propsData: { - actions: actionsMock, - }, - }).$mount(); - }); + beforeEach(() => { + vm = mountComponent(Component, { actions }); + }); + + it('should render a dropdown button with icon and title attribute', () => { + expect(vm.$el.querySelector('.fa-caret-down')).toBeDefined(); + expect(vm.$el.querySelector('.dropdown-new').getAttribute('data-original-title')).toEqual( + 'Deploy to...', + ); - describe('computed', () => { - it('title', () => { - expect(component.title).toEqual('Deploy to...'); + expect(vm.$el.querySelector('.dropdown-new').getAttribute('aria-label')).toEqual( + 'Deploy to...', + ); }); - }); - it('should render a dropdown button with icon and title attribute', () => { - expect(component.$el.querySelector('.fa-caret-down')).toBeDefined(); - expect( - component.$el.querySelector('.dropdown-new').getAttribute('data-original-title'), - ).toEqual('Deploy to...'); + it('should render a dropdown with the provided list of actions', () => { + expect(vm.$el.querySelectorAll('.dropdown-menu li').length).toEqual(actions.length); + }); - expect(component.$el.querySelector('.dropdown-new').getAttribute('aria-label')).toEqual( - 'Deploy to...', - ); - }); + it("should render a disabled action when it's not playable", () => { + expect( + vm.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'), + ).toEqual('disabled'); - it('should render a dropdown with the provided list of actions', () => { - expect(component.$el.querySelectorAll('.dropdown-menu li').length).toEqual(actionsMock.length); + expect( + vm.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'), + ).toEqual(true); + }); }); - it("should render a disabled action when it's not playable", () => { - expect( - component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'), - ).toEqual('disabled'); + describe('scheduled jobs', () => { + const scheduledJobAction = { + name: 'scheduled action', + playPath: `${TEST_HOST}/scheduled/job/action`, + playable: true, + scheduledAt: '2063-04-05T00:42:00Z', + }; + const expiredJobAction = { + name: 'expired action', + playPath: `${TEST_HOST}/expired/job/action`, + playable: true, + scheduledAt: '2018-10-05T08:23:00Z', + }; + const findDropdownItem = action => { + const buttons = vm.$el.querySelectorAll('.dropdown-menu li button'); + return Array.prototype.find.call(buttons, element => + element.innerText.trim().startsWith(action.name), + ); + }; + + beforeEach(() => { + spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime()); + vm = mountComponent(Component, { actions: [scheduledJobAction, expiredJobAction] }); + }); + + it('emits postAction event after confirming', () => { + const emitSpy = jasmine.createSpy('emit'); + eventHub.$on('postAction', emitSpy); + spyOn(window, 'confirm').and.callFake(() => true); + + findDropdownItem(scheduledJobAction).click(); + + expect(window.confirm).toHaveBeenCalled(); + expect(emitSpy).toHaveBeenCalledWith({ endpoint: scheduledJobAction.playPath }); + }); + + it('does not emit postAction event if confirmation is cancelled', () => { + const emitSpy = jasmine.createSpy('emit'); + eventHub.$on('postAction', emitSpy); + spyOn(window, 'confirm').and.callFake(() => false); + + findDropdownItem(scheduledJobAction).click(); - expect( - component.$el - .querySelector('.dropdown-menu li:last-child button') - .classList.contains('disabled'), - ).toEqual(true); + expect(window.confirm).toHaveBeenCalled(); + expect(emitSpy).not.toHaveBeenCalled(); + }); + + it('displays the remaining time in the dropdown', () => { + expect(findDropdownItem(scheduledJobAction)).toContainText('24:00:00'); + }); + + it('displays 00:00:00 for expired jobs in the dropdown', () => { + expect(findDropdownItem(expiredJobAction)).toContainText('00:00:00'); + }); }); }); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index a81bdf618a3..9070d968cfd 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -19,7 +19,7 @@ describe('DiscussionFilter component', () => { }, ]; const Component = Vue.extend(DiscussionFilter); - const defaultValue = discussionFiltersMock[0].value; + const selectedValue = discussionFiltersMock[0].value; store.state.discussions = discussions; vm = mountComponentWithStore(Component, { @@ -27,7 +27,7 @@ describe('DiscussionFilter component', () => { store, props: { filters: discussionFiltersMock, - defaultValue, + selectedValue, }, }); }); @@ -63,4 +63,24 @@ describe('DiscussionFilter component', () => { expect(vm.filterDiscussion).not.toHaveBeenCalled(); }); + + it('disables commenting when "Show history only" filter is applied', () => { + const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + filterItem.click(); + + expect(vm.$store.state.commentsDisabled).toBe(true); + }); + + it('enables commenting when "Show history only" filter is not applied', () => { + const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button'); + filterItem.click(); + + expect(vm.$store.state.commentsDisabled).toBe(false); + }); + + it('renders a dropdown divider for the default filter', () => { + const defaultFilter = vm.$el.querySelector('.dropdown-menu li:first-child'); + + expect(defaultFilter.lastChild.classList).toContain('dropdown-divider'); + }); }); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 3e289a6b8e6..0081f42c330 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -121,6 +121,13 @@ describe('note_app', () => { ).toEqual('Write a comment or drag your files here…'); }); + it('should not render form when commenting is disabled', () => { + store.state.commentsDisabled = true; + vm = mountComponent(); + + expect(vm.$el.querySelector('.js-main-target-form')).toEqual(null); + }); + it('should render form comment button as disabled', () => { expect(vm.$el.querySelector('.js-note-new-discussion').getAttribute('disabled')).toEqual( 'disabled', diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index f4643fd55ed..0c0bc45b201 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -509,4 +509,17 @@ describe('Actions Notes Store', () => { expect(mrWidgetEventHub.$emit).toHaveBeenCalledWith('mr.discussion.updated'); }); }); + + describe('setCommentsDisabled', () => { + it('should set comments disabled state', done => { + testAction( + actions.setCommentsDisabled, + true, + null, + [{ type: 'DISABLE_COMMENTS', payload: true }], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 380ab59099d..461de5a3106 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -427,4 +427,14 @@ describe('Notes Store mutations', () => { expect(state.discussions[0].expanded).toBe(true); }); }); + + describe('DISABLE_COMMENTS', () => { + it('should set comments disabled state', () => { + const state = {}; + + mutations.DISABLE_COMMENTS(state, true); + + expect(state.commentsDisabled).toEqual(true); + }); + }); }); diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js index f58515daa4f..69767d9cf1c 100644 --- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js +++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js @@ -151,11 +151,11 @@ describe('Grouped Test Reports App', () => { it('renders resolved failures', done => { setTimeout(() => { - expect(vm.$el.querySelector('.js-mr-code-resolved-issues').textContent).toContain( + expect(vm.$el.querySelector('.report-block-container').textContent).toContain( resolvedFailures.suites[0].resolved_failures[0].name, ); - expect(vm.$el.querySelector('.js-mr-code-resolved-issues').textContent).toContain( + expect(vm.$el.querySelector('.report-block-container').textContent).toContain( resolvedFailures.suites[0].resolved_failures[1].name, ); done(); diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js index eb7307605d7..b02af8baaec 100644 --- a/spec/javascripts/reports/components/report_section_spec.js +++ b/spec/javascripts/reports/components/report_section_spec.js @@ -120,7 +120,7 @@ describe('Report section', () => { 'Code quality improved on 1 point and degraded on 1 point', ); - expect(vm.$el.querySelectorAll('.js-mr-code-resolved-issues li').length).toEqual( + expect(vm.$el.querySelectorAll('.report-block-container li').length).toEqual( resolvedIssues.length, ); }); diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index 3d44af11153..2f1bd00fa10 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -242,6 +242,10 @@ describe('Deployment component', () => { it('renders information about running deployment', () => { expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to'); }); + + it('renders disabled stop button', () => { + expect(vm.$el.querySelector('.js-stop-env').getAttribute('disabled')).toBe('disabled'); + }); }); describe('success', () => { diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index 6c7637eed13..d905bbe4040 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -73,7 +73,7 @@ describe('MRWidgetPipeline', () => { }); expect(vm.$el.querySelector('.media-body').textContent.trim()).toContain( - 'Could not retrieve the pipeline status. For troubleshooting steps, read the <a href="help">documentation.</a>', + 'Could not retrieve the pipeline status. For troubleshooting steps, read the documentation.', ); }); diff --git a/spec/javascripts/vue_shared/components/smart_virtual_list_spec.js b/spec/javascripts/vue_shared/components/smart_virtual_list_spec.js new file mode 100644 index 00000000000..e723fead65e --- /dev/null +++ b/spec/javascripts/vue_shared/components/smart_virtual_list_spec.js @@ -0,0 +1,83 @@ +import Vue from 'vue'; +import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Toggle Button', () => { + let vm; + + const createComponent = ({ length, remain }) => { + const smartListProperties = { + rtag: 'section', + wtag: 'ul', + wclass: 'test-class', + // Size in pixels does not matter for our tests here + size: 35, + length, + remain, + }; + + const Component = Vue.extend({ + components: { + SmartVirtualScrollList, + }, + smartListProperties, + items: Array(length).fill(1), + template: ` + <smart-virtual-scroll-list v-bind="$options.smartListProperties"> + <li v-for="(val, key) in $options.items" :key="key">{{ key + 1 }}</li> + </smart-virtual-scroll-list>`, + }); + + return mountComponent(Component); + }; + + afterEach(() => { + vm.$destroy(); + }); + + describe('if the list is shorter than the maximum shown elements', () => { + const listLength = 10; + + beforeEach(() => { + vm = createComponent({ length: listLength, remain: 20 }); + }); + + it('renders without the vue-virtual-scroll-list component', () => { + expect(vm.$el.classList).not.toContain('js-virtual-list'); + expect(vm.$el.classList).toContain('js-plain-element'); + }); + + it('renders list with provided tags and classes for the wrapper elements', () => { + expect(vm.$el.tagName).toEqual('SECTION'); + expect(vm.$el.firstChild.tagName).toEqual('UL'); + expect(vm.$el.firstChild.classList).toContain('test-class'); + }); + + it('renders all children list elements', () => { + expect(vm.$el.querySelectorAll('li').length).toEqual(listLength); + }); + }); + + describe('if the list is longer than the maximum shown elements', () => { + const maxItemsShown = 20; + + beforeEach(() => { + vm = createComponent({ length: 1000, remain: maxItemsShown }); + }); + + it('uses the vue-virtual-scroll-list component', () => { + expect(vm.$el.classList).toContain('js-virtual-list'); + expect(vm.$el.classList).not.toContain('js-plain-element'); + }); + + it('renders list with provided tags and classes for the wrapper elements', () => { + expect(vm.$el.tagName).toEqual('SECTION'); + expect(vm.$el.firstChild.tagName).toEqual('UL'); + expect(vm.$el.firstChild.classList).toContain('test-class'); + }); + + it('renders at max twice the maximum shown elements', () => { + expect(vm.$el.querySelectorAll('li').length).toBeLessThanOrEqual(2 * maxItemsShown); + }); + }); +}); diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb new file mode 100644 index 00000000000..4f1b01eed41 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do + let(:migration) { described_class.new } + let(:clusters) { create_list(:cluster, 10, :project, :provided_by_gcp) } + + before do + clusters + end + + shared_examples 'consistent kubernetes namespace attributes' do + it 'should populate namespace and service account information' do + subject + + clusters_with_namespace.each do |cluster| + project = cluster.project + cluster_project = cluster.cluster_projects.first + namespace = "#{project.path}-#{project.id}" + kubernetes_namespace = cluster.reload.kubernetes_namespace + + expect(kubernetes_namespace).to be_present + expect(kubernetes_namespace.cluster_project).to eq(cluster_project) + expect(kubernetes_namespace.project).to eq(cluster_project.project) + expect(kubernetes_namespace.cluster).to eq(cluster_project.cluster) + expect(kubernetes_namespace.namespace).to eq(namespace) + expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") + end + end + end + + subject { migration.perform } + + context 'when no Clusters::Project has a Clusters::KubernetesNamespace' do + let(:cluster_projects) { Clusters::Project.all } + + it 'should create a Clusters::KubernetesNamespace per Clusters::Project' do + expect do + subject + end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects.count) + end + + it_behaves_like 'consistent kubernetes namespace attributes' do + let(:clusters_with_namespace) { clusters } + end + end + + context 'when every Clusters::Project has Clusters::KubernetesNamespace' do + before do + clusters.each do |cluster| + create(:cluster_kubernetes_namespace, + cluster_project: cluster.cluster_projects.first, + cluster: cluster, + project: cluster.project) + end + end + + it 'should not create any Clusters::KubernetesNamespace' do + expect do + subject + end.not_to change(Clusters::KubernetesNamespace, :count) + end + end + + context 'when only some Clusters::Project have Clusters::KubernetesNamespace related' do + let(:with_kubernetes_namespace) { clusters.first(6) } + let(:with_no_kubernetes_namespace) { clusters.last(4) } + + before do + with_kubernetes_namespace.each do |cluster| + create(:cluster_kubernetes_namespace, + cluster_project: cluster.cluster_projects.first, + cluster: cluster, + project: cluster.project) + end + end + + it 'creates limited number of Clusters::KubernetesNamespace' do + expect do + subject + end.to change(Clusters::KubernetesNamespace, :count).by(with_no_kubernetes_namespace.count) + end + + it 'should not modify clusters with Clusters::KubernetesNamespace' do + subject + + with_kubernetes_namespace.each do |cluster| + expect(cluster.kubernetes_namespaces.count).to eq(1) + end + end + + it_behaves_like 'consistent kubernetes namespace attributes' do + let(:clusters_with_namespace) { with_no_kubernetes_namespace } + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 8095a231cf3..1140bfdf6c3 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::Ci::Config::Entry::Reports do where(:keyword, :file) do :junit | 'junit.xml' - :codequality | 'codequality.json' + :codequality | 'gl-code-quality-report.json' :sast | 'gl-sast-report.json' :dependency_scanning | 'gl-dependency-scanning-report.json' :container_scanning | 'gl-container-scanning-report.json' diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb index 2e67c1c7f78..f8009709ce2 100644 --- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb @@ -44,15 +44,15 @@ describe Gitlab::CycleAnalytics::StageSummary do describe "#deploys" do it "finds the number of deploys made created after the 'from date'" do - Timecop.freeze(5.days.ago) { create(:deployment, project: project) } - Timecop.freeze(5.days.from_now) { create(:deployment, project: project) } + Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) } + Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) } expect(subject.third[:value]).to eq(1) end it "doesn't find commits from other projects" do Timecop.freeze(5.days.from_now) do - create(:deployment, project: create(:project, :repository)) + create(:deployment, :success, project: create(:project, :repository)) end expect(subject.third[:value]).to eq(0) diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index 294ec2c2fd6..edab53247e9 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -15,7 +15,12 @@ describe Gitlab::FileDetector do describe '.type_of' do it 'returns the type of a README file' do - expect(described_class.type_of('README.md')).to eq(:readme) + %w[README readme INDEX index].each do |filename| + expect(described_class.type_of(filename)).to eq(:readme) + %w[.md .adoc .rst].each do |extname| + expect(described_class.type_of(filename + extname)).to eq(:readme) + end + end end it 'returns nil for a README file in a directory' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index a63f34b5536..f4efa450cca 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -299,6 +299,7 @@ project: - ci_cd_settings - import_export_upload - repository_languages +- pool_repository award_emoji: - awardable - user diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index b333b334f36..c92bc92c42d 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do it 'should generate the appropriate specifications for the container' do container = subject.generate.spec.containers.first expect(container.name).to eq('helm') - expect(container.image).to eq('alpine:3.6') + expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.7.2-kube-1.11.0') expect(container.env.count).to eq(3) expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT]) expect(container.command).to match_array(["/bin/sh"]) diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb index da3f5d27b25..a1a59533bfb 100644 --- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb +++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do let(:role_ref) do { apiGroup: 'rbac.authorization.k8s.io', - kind: 'Role', + kind: 'ClusterRole', name: role_name } end @@ -35,6 +35,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do subject do described_class.new( + name: "gitlab-#{namespace}", role_name: role_name, namespace: namespace, service_account_name: service_account_name diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb index 194cae8c645..eceacac58af 100644 --- a/spec/lib/gitlab/slash_commands/command_spec.rb +++ b/spec/lib/gitlab/slash_commands/command_spec.rb @@ -44,7 +44,7 @@ describe Gitlab::SlashCommands::Command do let!(:build) { create(:ci_build, pipeline: pipeline) } let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:staging) { create(:environment, name: 'staging', project: project) } - let!(:deployment) { create(:deployment, environment: staging, deployable: build) } + let!(:deployment) { create(:deployment, :success, environment: staging, deployable: build) } let!(:manual) do create(:ci_build, :manual, pipeline: pipeline, diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb index 0d57334aa4c..25f3e8a0409 100644 --- a/spec/lib/gitlab/slash_commands/deploy_spec.rb +++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::SlashCommands::Deploy do let!(:staging) { create(:environment, name: 'staging', project: project) } let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:build) { create(:ci_build, pipeline: pipeline) } - let!(:deployment) { create(:deployment, environment: staging, deployable: build) } + let!(:deployment) { create(:deployment, :success, environment: staging, deployable: build) } context 'without actions' do it 'does not execute an action' do diff --git a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb index becb71cf427..4af51217031 100644 --- a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb +++ b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb @@ -65,6 +65,21 @@ describe DeleteInconsistentInternalIdRecords, :migration do context 'for deployments' do let(:scope) { :deployment } + let(:deployments) { table(:deployments) } + let(:internal_ids) { table(:internal_ids) } + + before do + internal_ids.create!(project_id: project1.id, usage: 2, last_value: 2) + internal_ids.create!(project_id: project2.id, usage: 2, last_value: 2) + internal_ids.create!(project_id: project3.id, usage: 2, last_value: 2) + end + + let(:create_models) do + 3.times { |i| deployments.create!(project_id: project1.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) } + 3.times { |i| deployments.create!(project_id: project2.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) } + 3.times { |i| deployments.create!(project_id: project3.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) } + end + it_behaves_like 'deleting inconsistent internal_id records' end diff --git a/spec/migrations/fill_empty_finished_at_in_deployments_spec.rb b/spec/migrations/fill_empty_finished_at_in_deployments_spec.rb new file mode 100644 index 00000000000..cf5c10f77e1 --- /dev/null +++ b/spec/migrations/fill_empty_finished_at_in_deployments_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20181030135124_fill_empty_finished_at_in_deployments') + +describe FillEmptyFinishedAtInDeployments, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:environments) { table(:environments) } + let(:deployments) { table(:deployments) } + + context 'when a deployment row does not have a value on finished_at' do + context 'when a deployment succeeded' do + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + environments.create!(id: 1, name: 'production', slug: 'production', project_id: 1) + deployments.create!(id: 1, iid: 1, project_id: 1, environment_id: 1, ref: 'master', sha: 'xxx', tag: false) + end + + it 'correctly replicates finished_at by created_at' do + expect(deployments.last.created_at).not_to be_nil + expect(deployments.last.finished_at).to be_nil + + migrate! + + expect(deployments.last.created_at).not_to be_nil + expect(deployments.last.finished_at).to eq(deployments.last.created_at) + end + end + + context 'when a deployment is running' do + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + environments.create!(id: 1, name: 'production', slug: 'production', project_id: 1) + deployments.create!(id: 1, iid: 1, project_id: 1, environment_id: 1, ref: 'master', sha: 'xxx', tag: false, status: 1) + end + + it 'does not fill finished_at' do + expect(deployments.last.created_at).not_to be_nil + expect(deployments.last.finished_at).to be_nil + + migrate! + + expect(deployments.last.created_at).not_to be_nil + expect(deployments.last.finished_at).to be_nil + end + end + end + + context 'when a deployment row does has a value on finished_at' do + let(:finished_at) { '2018-10-30 11:12:02 UTC' } + + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + environments.create!(id: 1, name: 'production', slug: 'production', project_id: 1) + deployments.create!(id: 1, iid: 1, project_id: 1, environment_id: 1, ref: 'master', sha: 'xxx', tag: false, finished_at: finished_at) + end + + it 'does not affect existing value' do + expect(deployments.last.created_at).not_to be_nil + expect(deployments.last.finished_at).not_to be_nil + + migrate! + + expect(deployments.last.created_at).not_to be_nil + expect(deployments.last.finished_at).to eq(finished_at) + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 87b91286168..95ae7bd21ab 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -594,4 +594,24 @@ describe ApplicationSetting do end end end + + describe '#archive_builds_older_than' do + subject { setting.archive_builds_older_than } + + context 'when the archive_builds_in_seconds is set' do + before do + setting.archive_builds_in_seconds = 3600 + end + + it { is_expected.to be_within(1.minute).of(1.hour.ago) } + end + + context 'when the archive_builds_in_seconds is set' do + before do + setting.archive_builds_in_seconds = nil + end + + it { is_expected.to be_nil } + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 65e06f27f35..2e65a6a2a0f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -17,8 +17,8 @@ describe Ci::Build do it { is_expected.to belong_to(:runner) } it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } - it { is_expected.to have_many(:deployments) } it { is_expected.to have_many(:trace_sections)} + it { is_expected.to have_one(:deployment) } it { is_expected.to have_one(:runner_session)} it { is_expected.to validate_presence_of(:ref) } it { is_expected.to respond_to(:has_trace?) } @@ -216,14 +216,6 @@ describe Ci::Build do let(:build) { create(:ci_build, :created, :schedulable, project: project) } it { expect(subject).to be_truthy } - - context 'when feature flag is diabled' do - before do - stub_feature_flags(ci_enable_scheduled_build: false) - end - - it { expect(subject).to be_falsy } - end end context 'when build is not schedulable' do @@ -327,10 +319,6 @@ describe Ci::Build do describe '#enqueue_scheduled' do subject { build.enqueue_scheduled } - before do - stub_feature_flags(ci_enable_scheduled_build: true) - end - context 'when build is scheduled and the right time has not come yet' do let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) } @@ -811,17 +799,100 @@ describe Ci::Build do end end + describe 'state transition as a deployable' do + let!(:build) { create(:ci_build, :start_review_app) } + let(:deployment) { build.deployment } + let(:environment) { deployment.environment } + + it 'has deployments record with created status' do + expect(deployment).to be_created + expect(environment.name).to eq('review/master') + end + + context 'when transits to running' do + before do + build.run! + end + + it 'transits deployment status to running' do + expect(deployment).to be_running + end + end + + context 'when transits to success' do + before do + allow(Deployments::SuccessWorker).to receive(:perform_async) + build.success! + end + + it 'transits deployment status to success' do + expect(deployment).to be_success + end + end + + context 'when transits to failed' do + before do + build.drop! + end + + it 'transits deployment status to failed' do + expect(deployment).to be_failed + end + end + + context 'when transits to skipped' do + before do + build.skip! + end + + it 'transits deployment status to canceled' do + expect(deployment).to be_canceled + end + end + + context 'when transits to canceled' do + before do + build.cancel! + end + + it 'transits deployment status to canceled' do + expect(deployment).to be_canceled + end + end + end + + describe '#on_stop' do + subject { build.on_stop } + + context 'when a job has a specification that it can be stopped from the other job' do + let(:build) { create(:ci_build, :start_review_app) } + + it 'returns the other job name' do + is_expected.to eq('stop_review_app') + end + end + + context 'when a job does not have environment information' do + let(:build) { create(:ci_build) } + + it 'returns nil' do + is_expected.to be_nil + end + end + end + describe 'deployment' do - describe '#last_deployment' do - subject { build.last_deployment } + describe '#has_deployment?' do + subject { build.has_deployment? } + + context 'when build has a deployment' do + let!(:deployment) { create(:deployment, deployable: build) } - context 'when multiple deployments are created' do - let!(:deployment1) { create(:deployment, deployable: build) } - let!(:deployment2) { create(:deployment, deployable: build) } + it { is_expected.to be_truthy } + end - it 'returns the latest one' do - is_expected.to eq(deployment2) - end + context 'when build does not have a deployment' do + it { is_expected.to be_falsy } end end @@ -830,14 +901,14 @@ describe Ci::Build do context 'when build succeeded' do let(:build) { create(:ci_build, :success) } - let!(:deployment) { create(:deployment, deployable: build) } + let!(:deployment) { create(:deployment, :success, deployable: build) } context 'current deployment is latest' do it { is_expected.to be_falsey } end context 'current deployment is not latest on environment' do - let!(:deployment2) { create(:deployment, environment: deployment.environment) } + let!(:deployment2) { create(:deployment, :success, environment: deployment.environment) } it { is_expected.to be_truthy } end @@ -1326,6 +1397,14 @@ describe Ci::Build do it { is_expected.not_to be_retryable } end + + context 'when build is degenerated' do + before do + build.degenerate! + end + + it { is_expected.not_to be_retryable } + end end end @@ -1408,6 +1487,14 @@ describe Ci::Build do expect(subject.retries_max).to eq 0 end end + + context 'when build is degenerated' do + subject { create(:ci_build, :degenerated) } + + it 'returns zero' do + expect(subject.retries_max).to eq 0 + end + end end end @@ -1523,11 +1610,11 @@ describe Ci::Build do end end - describe '#other_actions' do + describe '#other_manual_actions' do let(:build) { create(:ci_build, :manual, pipeline: pipeline) } let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') } - subject { build.other_actions } + subject { build.other_manual_actions } before do project.add_developer(user) @@ -1558,6 +1645,48 @@ describe Ci::Build do end end + describe '#other_scheduled_actions' do + let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) } + + subject { build.other_scheduled_actions } + + before do + project.add_developer(user) + end + + context "when other build's status is success" do + let!(:other_build) { create(:ci_build, :schedulable, :success, pipeline: pipeline, name: 'other action') } + + it 'returns other actions' do + is_expected.to contain_exactly(other_build) + end + end + + context "when other build's status is failed" do + let!(:other_build) { create(:ci_build, :schedulable, :failed, pipeline: pipeline, name: 'other action') } + + it 'returns other actions' do + is_expected.to contain_exactly(other_build) + end + end + + context "when other build's status is running" do + let!(:other_build) { create(:ci_build, :schedulable, :running, pipeline: pipeline, name: 'other action') } + + it 'does not return other actions' do + is_expected.to be_empty + end + end + + context "when other build's status is scheduled" do + let!(:other_build) { create(:ci_build, :scheduled, pipeline: pipeline, name: 'other action') } + + it 'does not return other actions' do + is_expected.to contain_exactly(other_build) + end + end + end + describe '#persisted_environment' do let!(:environment) do create(:environment, project: project, name: "foo-#{project.default_branch}") @@ -1629,6 +1758,12 @@ describe Ci::Build do it { is_expected.to be_playable } end + + context 'when build is a manual and degenerated' do + subject { build_stubbed(:ci_build, :manual, :degenerated, status: :manual) } + + it { is_expected.not_to be_playable } + end end context 'when build is scheduled' do @@ -3157,10 +3292,14 @@ describe Ci::Build do end describe '#deployment_status' do + before do + allow_any_instance_of(described_class).to receive(:create_deployment) + end + context 'when build is a last deployment' do let(:build) { create(:ci_build, :success, environment: 'production') } let(:environment) { create(:environment, name: 'production', project: build.project) } - let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) } + let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } it { expect(build.deployment_status).to eq(:last) } end @@ -3168,8 +3307,8 @@ describe Ci::Build do context 'when there is a newer build with deployment' do let(:build) { create(:ci_build, :success, environment: 'production') } let(:environment) { create(:environment, name: 'production', project: build.project) } - let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) } - let!(:last_deployment) { create(:deployment, environment: environment, project: environment.project) } + let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } + let!(:last_deployment) { create(:deployment, :success, environment: environment, project: environment.project) } it { expect(build.deployment_status).to eq(:out_of_date) } end @@ -3177,7 +3316,7 @@ describe Ci::Build do context 'when build with deployment has failed' do let(:build) { create(:ci_build, :failed, environment: 'production') } let(:environment) { create(:environment, name: 'production', project: build.project) } - let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) } + let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } it { expect(build.deployment_status).to eq(:failed) } end @@ -3185,16 +3324,59 @@ describe Ci::Build do context 'when build with deployment is running' do let(:build) { create(:ci_build, environment: 'production') } let(:environment) { create(:environment, name: 'production', project: build.project) } - let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) } + let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } it { expect(build.deployment_status).to eq(:creating) } end + end - context 'when build is successful but deployment is not ready yet' do - let(:build) { create(:ci_build, :success, environment: 'production') } - let(:environment) { create(:environment, name: 'production', project: build.project) } + describe '#degenerated?' do + context 'when build is degenerated' do + subject { create(:ci_build, :degenerated) } - it { expect(build.deployment_status).to eq(:creating) } + it { is_expected.to be_degenerated } + end + + context 'when build is valid' do + subject { create(:ci_build) } + + it { is_expected.not_to be_degenerated } + + context 'and becomes degenerated' do + before do + subject.degenerate! + end + + it { is_expected.to be_degenerated } + end + end + end + + describe '#archived?' do + context 'when build is degenerated' do + subject { create(:ci_build, :degenerated) } + + it { is_expected.to be_archived } + end + + context 'for old build' do + subject { create(:ci_build, created_at: 1.day.ago) } + + context 'when archive_builds_in is set' do + before do + stub_application_setting(archive_builds_in_seconds: 3600) + end + + it { is_expected.to be_archived } + end + + context 'when archive_builds_in is not set' do + before do + stub_application_setting(archive_builds_in_seconds: nil) + end + + it { is_expected.not_to be_archived } + end end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 153244b2159..9e6146b8a44 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1043,6 +1043,11 @@ describe Ci::Pipeline, :mailer do expect(described_class.newest_first.pluck(:status)) .to eq(%w[skipped failed success canceled]) end + + it 'searches limited backlog' do + expect(described_class.newest_first(limit: 1).pluck(:status)) + .to eq(%w[skipped]) + end end describe '.latest_status' do @@ -1148,6 +1153,19 @@ describe Ci::Pipeline, :mailer do end end + describe '.latest_successful_ids_per_project' do + let(:projects) { create_list(:project, 2) } + let!(:pipeline1) { create(:ci_pipeline, :success, project: projects[0]) } + let!(:pipeline2) { create(:ci_pipeline, :success, project: projects[0]) } + let!(:pipeline3) { create(:ci_pipeline, :failed, project: projects[0]) } + let!(:pipeline4) { create(:ci_pipeline, :success, project: projects[1]) } + + it 'returns expected pipeline ids' do + expect(described_class.latest_successful_ids_per_project) + .to contain_exactly(pipeline2, pipeline4) + end + end + describe '.internal_sources' do subject { described_class.internal_sources } diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index f9776acd4c8..48ba163b38c 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -109,14 +109,20 @@ describe Clusters::Applications::Prometheus do end context 'cluster has kubeclient' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url } let(:kube_client) { subject.cluster.kubeclient.core_client } - subject { create(:clusters_applications_prometheus) } + subject { create(:clusters_applications_prometheus, cluster: cluster) } before do subject.cluster.platform_kubernetes.namespace = 'a-namespace' - stub_kubeclient_discover(subject.cluster.platform_kubernetes.api_url) + stub_kubeclient_discover(cluster.platform_kubernetes.api_url) + + create(:cluster_kubernetes_namespace, + cluster: cluster, + cluster_project: cluster.cluster_project, + project: cluster.cluster_project.project) end it 'creates proxy prometheus rest client' do diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index c245e8df815..19b76ca8cfb 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -16,6 +16,7 @@ describe Clusters::Cluster do it { is_expected.to have_one(:application_runner) } it { is_expected.to have_many(:kubernetes_namespaces) } it { is_expected.to have_one(:kubernetes_namespace) } + it { is_expected.to have_one(:cluster_project) } it { is_expected.to delegate_method(:status).to(:provider) } it { is_expected.to delegate_method(:status_reason).to(:provider) } diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index dea58fa26c7..0dfeea5cd2f 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -10,23 +10,15 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do describe 'namespace uniqueness validation' do let(:cluster_project) { create(:cluster_project) } - - let(:kubernetes_namespace) do - build(:cluster_kubernetes_namespace, - cluster: cluster_project.cluster, - project: cluster_project.project, - cluster_project: cluster_project) - end + let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') } subject { kubernetes_namespace } context 'when cluster is using the namespace' do before do create(:cluster_kubernetes_namespace, - cluster: cluster_project.cluster, - project: cluster_project.project, - cluster_project: cluster_project, - namespace: kubernetes_namespace.namespace) + cluster: kubernetes_namespace.cluster, + namespace: 'my-namespace') end it { is_expected.not_to be_valid } @@ -37,48 +29,79 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do end end - describe '#set_namespace_and_service_account_to_default' do - let(:cluster) { platform.cluster } - let(:cluster_project) { create(:cluster_project, cluster: cluster) } - let(:kubernetes_namespace) do - create(:cluster_kubernetes_namespace, - cluster: cluster_project.cluster, - project: cluster_project.project, - cluster_project: cluster_project) - end + describe '#configure_predefined_variables' do + let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) } + let(:cluster) { kubernetes_namespace.cluster } + let(:platform) { kubernetes_namespace.platform_kubernetes } - describe 'namespace' do - let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) } + subject { kubernetes_namespace.configure_predefined_credentials } - subject { kubernetes_namespace.namespace } + describe 'namespace' do + before do + platform.update_column(:namespace, namespace) + end context 'when platform has a namespace assigned' do let(:namespace) { 'platform-namespace' } it 'should copy the namespace' do - is_expected.to eq('platform-namespace') + subject + + expect(kubernetes_namespace.namespace).to eq('platform-namespace') end end context 'when platform does not have namespace assigned' do + let(:project) { kubernetes_namespace.project } let(:namespace) { nil } + let(:project_slug) { "#{project.path}-#{project.id}" } - it 'should set default namespace' do - project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}" + it 'should fallback to project namespace' do + subject - is_expected.to eq(project_slug) + expect(kubernetes_namespace.namespace).to eq(project_slug) end end end describe 'service_account_name' do - let(:platform) { create(:cluster_platform_kubernetes) } - - subject { kubernetes_namespace.service_account_name } + let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" } it 'should set a service account name based on namespace' do - is_expected.to eq("#{kubernetes_namespace.namespace}-service-account") + subject + + expect(kubernetes_namespace.service_account_name).to eq(service_account_name) end end end + + describe '#predefined_variables' do + let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, service_account_token: token) } + let(:cluster) { create(:cluster, :project, platform_kubernetes: platform) } + let(:platform) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) } + + let(:api_url) { 'https://kube.domain.com' } + let(:ca_pem) { 'CA PEM DATA' } + let(:token) { 'token' } + + let(:kubeconfig) do + config_file = expand_fixture_path('config/kubeconfig.yml') + config = YAML.safe_load(File.read(config_file)) + config.dig('users', 0, 'user')['token'] = token + config.dig('contexts', 0, 'context')['namespace'] = kubernetes_namespace.namespace + config.dig('clusters', 0, 'cluster')['certificate-authority-data'] = + Base64.strict_encode64(ca_pem) + + YAML.dump(config) + end + + it 'sets the variables' do + expect(kubernetes_namespace.predefined_variables).to include( + { key: 'KUBE_SERVICE_ACCOUNT', value: kubernetes_namespace.service_account_name, public: true }, + { key: 'KUBE_NAMESPACE', value: kubernetes_namespace.namespace, public: true }, + { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }, + { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true } + ) + end + end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index e13eb554add..2bcccc8184a 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -124,9 +124,17 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end describe '#kubeclient' do + let(:cluster) { create(:cluster, :project) } + let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace', cluster: cluster) } + subject { kubernetes.kubeclient } - let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace') } + before do + create(:cluster_kubernetes_namespace, + cluster: kubernetes.cluster, + cluster_project: kubernetes.cluster.cluster_project, + project: kubernetes.cluster.cluster_project.project) + end it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) } end @@ -186,29 +194,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching describe '#predefined_variables' do let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) } - let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) } + let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) } let(:api_url) { 'https://kube.domain.com' } let(:ca_pem) { 'CA PEM DATA' } - let(:token) { 'token' } - - let(:kubeconfig) do - config_file = expand_fixture_path('config/kubeconfig.yml') - config = YAML.load(File.read(config_file)) - config.dig('users', 0, 'user')['token'] = token - config.dig('contexts', 0, 'context')['namespace'] = namespace - config.dig('clusters', 0, 'cluster')['certificate-authority-data'] = - Base64.strict_encode64(ca_pem) - - YAML.dump(config) - end shared_examples 'setting variables' do it 'sets the variables' do - expect(kubernetes.predefined_variables).to include( + expect(kubernetes.predefined_variables(project: cluster.project)).to include( { key: 'KUBE_URL', value: api_url, public: true }, - { key: 'KUBE_TOKEN', value: token, public: false }, - { key: 'KUBE_NAMESPACE', value: namespace, public: true }, - { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }, { key: 'KUBE_CA_PEM', value: ca_pem, public: true }, { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true } ) @@ -229,13 +222,6 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching let(:namespace) { kubernetes.actual_namespace } it_behaves_like 'setting variables' - - it 'sets the KUBE_NAMESPACE' do - kube_namespace = kubernetes.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' } - - expect(kube_namespace).not_to be_nil - expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) - end end end @@ -319,4 +305,27 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to include(pods: []) } end end + + describe '#update_kubernetes_namespace' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + let(:platform) { cluster.platform } + + context 'when namespace is updated' do + it 'should call ConfigureWorker' do + expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id).once + + platform.namespace = 'new-namespace' + platform.save + end + end + + context 'when namespace is not updated' do + it 'should not call ConfigureWorker' do + expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async) + + platform.username = "new-username" + platform.save + end + end + end end diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb index debc02fa51f..5713106418d 100644 --- a/spec/models/concerns/awardable_spec.rb +++ b/spec/models/concerns/awardable_spec.rb @@ -37,8 +37,8 @@ describe Awardable do create(:award_emoji, awardable: issue3, name: "star", user: award_emoji.user) create(:award_emoji, awardable: issue3, name: "star", user: award_emoji2.user) - expect(Issue.awarded(award_emoji.user)).to eq [issue, issue3] - expect(Issue.awarded(award_emoji2.user)).to eq [issue2, issue3] + expect(Issue.awarded(award_emoji.user)).to contain_exactly(issue, issue3) + expect(Issue.awarded(award_emoji2.user)).to contain_exactly(issue2, issue3) end end diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb new file mode 100644 index 00000000000..ac79c75a55e --- /dev/null +++ b/spec/models/concerns/deployable_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +describe Deployable do + describe '#create_deployment' do + let(:deployment) { job.deployment } + let(:environment) { deployment&.environment } + + before do + job.reload + end + + context 'when the deployable object will deploy to production' do + let!(:job) { create(:ci_build, :start_review_app) } + + it 'creates a deployment and environment record' do + expect(deployment.project).to eq(job.project) + expect(deployment.ref).to eq(job.ref) + expect(deployment.tag).to eq(job.tag) + expect(deployment.sha).to eq(job.sha) + expect(deployment.user).to eq(job.user) + expect(deployment.deployable).to eq(job) + expect(deployment.on_stop).to eq('stop_review_app') + expect(environment.name).to eq('review/master') + end + end + + context 'when the deployable object will stop an environment' do + let!(:job) { create(:ci_build, :stop_review_app) } + + it 'does not create a deployment record' do + expect(deployment).to be_nil + end + end + + context 'when the deployable object has already had a deployment' do + let!(:job) { create(:ci_build, :start_review_app, deployment: race_deployment) } + let!(:race_deployment) { create(:deployment, :success) } + + it 'does not create a new deployment' do + expect(deployment).to eq(race_deployment) + end + end + + context 'when the deployable object will not deploy' do + let!(:job) { create(:ci_build) } + + it 'does not create a deployment and environment record' do + expect(deployment).to be_nil + expect(environment).to be_nil + end + end + end +end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index b8364e0cf88..06c1e9c8c6a 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -16,6 +16,22 @@ describe Deployment do it { is_expected.to validate_presence_of(:ref) } it { is_expected.to validate_presence_of(:sha) } + describe '#scheduled_actions' do + subject { deployment.scheduled_actions } + + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let(:deployment) { create(:deployment, deployable: build) } + + it 'delegates to other_scheduled_actions' do + expect_any_instance_of(Ci::Build) + .to receive(:other_scheduled_actions) + + subject + end + end + describe 'modules' do it_behaves_like 'AtomicInternalId' do let(:internal_id_attribute) { :iid } @@ -26,16 +42,174 @@ describe Deployment do end end - describe 'after_create callbacks' do - let(:environment) { create(:environment) } - let(:store) { Gitlab::EtagCaching::Store.new } + describe '.success' do + subject { described_class.success } + + context 'when deployment status is success' do + let(:deployment) { create(:deployment, :success) } + + it { is_expected.to eq([deployment]) } + end + + context 'when deployment status is created' do + let(:deployment) { create(:deployment, :created) } + + it { is_expected.to be_empty } + end + + context 'when deployment status is running' do + let(:deployment) { create(:deployment, :running) } + + it { is_expected.to be_empty } + end + end + + describe 'state machine' do + context 'when deployment runs' do + let(:deployment) { create(:deployment) } + + before do + deployment.run! + end + + it 'starts running' do + Timecop.freeze do + expect(deployment).to be_running + expect(deployment.finished_at).to be_nil + end + end + end + + context 'when deployment succeeded' do + let(:deployment) { create(:deployment, :running) } + + it 'has correct status' do + Timecop.freeze do + deployment.succeed! + + expect(deployment).to be_success + expect(deployment.finished_at).to eq(Time.now) + end + end + + it 'executes Deployments::SuccessWorker asynchronously' do + expect(Deployments::SuccessWorker) + .to receive(:perform_async).with(deployment.id) + + deployment.succeed! + end + end + + context 'when deployment failed' do + let(:deployment) { create(:deployment, :running) } + + it 'has correct status' do + Timecop.freeze do + deployment.drop! + + expect(deployment).to be_failed + expect(deployment.finished_at).to eq(Time.now) + end + end + end + + context 'when deployment was canceled' do + let(:deployment) { create(:deployment, :running) } + + it 'has correct status' do + Timecop.freeze do + deployment.cancel! + + expect(deployment).to be_canceled + expect(deployment.finished_at).to eq(Time.now) + end + end + end + end + + describe '#success?' do + subject { deployment.success? } + + context 'when deployment status is success' do + let(:deployment) { create(:deployment, :success) } + + it { is_expected.to be_truthy } + end + + context 'when deployment status is failed' do + let(:deployment) { create(:deployment, :failed) } + + it { is_expected.to be_falsy } + end + end + + describe '#status_name' do + subject { deployment.status_name } + + context 'when deployment status is success' do + let(:deployment) { create(:deployment, :success) } + + it { is_expected.to eq(:success) } + end + + context 'when deployment status is failed' do + let(:deployment) { create(:deployment, :failed) } + + it { is_expected.to eq(:failed) } + end + end + + describe '#finished_at' do + subject { deployment.finished_at } - it 'invalidates the environment etag cache' do - old_value = store.get(environment.etag_cache_key) + context 'when deployment status is created' do + let(:deployment) { create(:deployment) } - create(:deployment, environment: environment) + it { is_expected.to be_nil } + end + + context 'when deployment status is success' do + let(:deployment) { create(:deployment, :success) } + + it { is_expected.to eq(deployment.read_attribute(:finished_at)) } + end - expect(store.get(environment.etag_cache_key)).not_to eq(old_value) + context 'when deployment status is success' do + let(:deployment) { create(:deployment, :success, finished_at: nil) } + + before do + deployment.update_column(:finished_at, nil) + end + + it { is_expected.to eq(deployment.read_attribute(:created_at)) } + end + + context 'when deployment status is running' do + let(:deployment) { create(:deployment, :running) } + + it { is_expected.to be_nil } + end + end + + describe '#deployed_at' do + subject { deployment.deployed_at } + + context 'when deployment status is created' do + let(:deployment) { create(:deployment) } + + it { is_expected.to be_nil } + end + + context 'when deployment status is success' do + let(:deployment) { create(:deployment, :success) } + + it { is_expected.to eq(deployment.read_attribute(:finished_at)) } + end + + context 'when deployment status is running' do + let(:deployment) { create(:deployment, :running) } + + it { is_expected.to be_nil } end end @@ -96,7 +270,7 @@ describe Deployment do end describe '#metrics' do - let(:deployment) { create(:deployment) } + let(:deployment) { create(:deployment, :success) } let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } subject { deployment.metrics } @@ -125,7 +299,7 @@ describe Deployment do describe '#additional_metrics' do let(:project) { create(:project, :repository) } - let(:deployment) { create(:deployment, project: project) } + let(:deployment) { create(:deployment, :succeed, project: project) } subject { deployment.additional_metrics } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 1de95d881a7..e121369f6ac 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -95,7 +95,7 @@ describe Environment do context 'with a last deployment' do let!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) + create(:deployment, :success, environment: environment, sha: project.commit('master').id) end context 'in the same branch' do @@ -136,8 +136,8 @@ describe Environment do describe '#first_deployment_for' do let(:project) { create(:project, :repository) } - let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } - let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } + let!(:deployment) { create(:deployment, :succeed, environment: environment, ref: commit.parent.id) } + let!(:deployment1) { create(:deployment, :succeed, environment: environment, ref: commit.id) } let(:head_commit) { project.commit } let(:commit) { project.commit.parent } @@ -181,7 +181,8 @@ describe Environment do let(:build) { create(:ci_build) } let!(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :success, + environment: environment, deployable: build, on_stop: 'close_app') end @@ -249,7 +250,8 @@ describe Environment do let(:build) { create(:ci_build, pipeline: pipeline) } let!(:deployment) do - create(:deployment, environment: environment, + create(:deployment, :success, + environment: environment, deployable: build, on_stop: 'close_app') end @@ -304,7 +306,7 @@ describe Environment do context 'when last deployment to environment is the most recent one' do before do - create(:deployment, environment: environment, ref: 'feature') + create(:deployment, :success, environment: environment, ref: 'feature') end it { is_expected.to be true } @@ -312,8 +314,8 @@ describe Environment do context 'when last deployment to environment is not the most recent' do before do - create(:deployment, environment: environment, ref: 'feature') - create(:deployment, environment: environment, ref: 'master') + create(:deployment, :success, environment: environment, ref: 'feature') + create(:deployment, :success, environment: environment, ref: 'master') end it { is_expected.to be false } @@ -321,7 +323,7 @@ describe Environment do end describe '#actions_for' do - let(:deployment) { create(:deployment, environment: environment) } + let(:deployment) { create(:deployment, :success, environment: environment) } let(:pipeline) { deployment.deployable.pipeline } let!(:review_action) { create(:ci_build, :manual, name: 'review-apps', pipeline: pipeline, environment: 'review/$CI_COMMIT_REF_NAME' )} let!(:production_action) { create(:ci_build, :manual, name: 'production', pipeline: pipeline, environment: 'production' )} @@ -331,6 +333,70 @@ describe Environment do end end + describe '.deployments' do + subject { environment.deployments } + + context 'when there is a deployment record with created status' do + let(:deployment) { create(:deployment, :created, environment: environment) } + + it 'does not return the record' do + is_expected.to be_empty + end + end + + context 'when there is a deployment record with running status' do + let(:deployment) { create(:deployment, :running, environment: environment) } + + it 'does not return the record' do + is_expected.to be_empty + end + end + + context 'when there is a deployment record with success status' do + let(:deployment) { create(:deployment, :success, environment: environment) } + + it 'returns the record' do + is_expected.to eq([deployment]) + end + end + end + + describe '.last_deployment' do + subject { environment.last_deployment } + + before do + allow_any_instance_of(Deployment).to receive(:create_ref) + end + + context 'when there is an old deployment record' do + let!(:previous_deployment) { create(:deployment, :success, environment: environment) } + + context 'when there is a deployment record with created status' do + let!(:deployment) { create(:deployment, environment: environment) } + + it 'returns the previous deployment' do + is_expected.to eq(previous_deployment) + end + end + + context 'when there is a deployment record with running status' do + let!(:deployment) { create(:deployment, :running, environment: environment) } + + it 'returns the previous deployment' do + is_expected.to eq(previous_deployment) + end + end + + context 'when there is a deployment record with success status' do + let!(:deployment) { create(:deployment, :success, environment: environment) } + + it 'returns the latest successful deployment' do + is_expected.to eq(deployment) + end + end + end + end + describe '#has_terminals?' do subject { environment.has_terminals? } @@ -338,7 +404,7 @@ describe Environment do context 'with a deployment service' do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do context 'and a deployment' do - let!(:deployment) { create(:deployment, environment: environment) } + let!(:deployment) { create(:deployment, :success, environment: environment) } it { is_expected.to be_truthy } end diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index e7805d52d75..52b98552184 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe EnvironmentStatus do - let(:deployment) { create(:deployment, :review_app) } + let(:deployment) { create(:deployment, :succeed, :review_app) } let(:environment) { deployment.environment} let(:project) { deployment.project } let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } @@ -12,7 +12,7 @@ describe EnvironmentStatus do it { is_expected.to delegate_method(:id).to(:environment) } it { is_expected.to delegate_method(:name).to(:environment) } it { is_expected.to delegate_method(:project).to(:environment) } - it { is_expected.to delegate_method(:deployed_at).to(:deployment).as(:created_at) } + it { is_expected.to delegate_method(:deployed_at).to(:deployment) } it { is_expected.to delegate_method(:status).to(:deployment) } describe '#project' do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 90cce826b6c..47e8f04e728 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -52,9 +52,9 @@ describe MergeRequestDiff do context 'when it was not cleaned by the system' do it 'returns persisted diffs' do - expect(diff).to receive(:load_diffs) + expect(diff).to receive(:load_diffs).and_call_original - diff.diffs + diff.diffs.diff_files end end @@ -76,19 +76,19 @@ describe MergeRequestDiff do end it 'returns persisted diffs if cannot compare with diff refs' do - expect(diff).to receive(:load_diffs) + expect(diff).to receive(:load_diffs).and_call_original diff.update!(head_commit_sha: 'invalid-sha') - diff.diffs + diff.diffs.diff_files end it 'returns persisted diffs if diff refs does not exist' do - expect(diff).to receive(:load_diffs) + expect(diff).to receive(:load_diffs).and_call_original diff.update!(start_commit_sha: nil, base_commit_sha: nil) - diff.diffs + diff.diffs.diff_files end end end @@ -211,4 +211,25 @@ describe MergeRequestDiff do expect(diff_with_commits.commits_count).to eq(29) end end + + describe '#commits_by_shas' do + let(:commit_shas) { diff_with_commits.commit_shas } + + it 'returns empty if no SHAs were provided' do + expect(diff_with_commits.commits_by_shas([])).to be_empty + end + + it 'returns one SHA' do + commits = diff_with_commits.commits_by_shas([commit_shas.first, Gitlab::Git::BLANK_SHA]) + + expect(commits.count).to eq(1) + end + + it 'returns all matching SHAs' do + commits = diff_with_commits.commits_by_shas(commit_shas) + + expect(commits.count).to eq(commit_shas.count) + expect(commits.map(&:sha)).to match_array(commit_shas) + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 85a4ebac66c..3a54725c7ec 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -552,9 +552,9 @@ describe MergeRequest do it 'delegates to the MR diffs' do merge_request.save - expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)) + expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)).and_call_original - merge_request.diffs(options) + merge_request.diffs(options).diff_files end end @@ -1836,8 +1836,8 @@ describe MergeRequest do let(:environments) { create_list(:environment, 3, project: project) } before do - create(:deployment, environment: environments.first, ref: 'master', sha: project.commit('master').id) - create(:deployment, environment: environments.second, ref: 'feature', sha: project.commit('feature').id) + create(:deployment, :success, environment: environments.first, ref: 'master', sha: project.commit('master').id) + create(:deployment, :success, environment: environments.second, ref: 'feature', sha: project.commit('feature').id) end it 'selects deployed environments' do @@ -1857,7 +1857,7 @@ describe MergeRequest do let(:source_environment) { create(:environment, project: source_project) } before do - create(:deployment, environment: source_environment, ref: 'feature', sha: merge_request.diff_head_sha) + create(:deployment, :success, environment: source_environment, ref: 'feature', sha: merge_request.diff_head_sha) end it 'selects deployed environments' do @@ -1868,7 +1868,7 @@ describe MergeRequest do let(:target_environment) { create(:environment, project: project) } before do - create(:deployment, environment: target_environment, tag: true, sha: merge_request.diff_head_sha) + create(:deployment, :success, environment: target_environment, tag: true, sha: merge_request.diff_head_sha) end it 'selects deployed environments' do @@ -2611,6 +2611,32 @@ describe MergeRequest do end end + describe '#includes_any_commits?' do + it 'returns false' do + expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey + end + + it 'returns true' do + expect(subject.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy + end + + it 'returns true even when there is a non-existent comit' do + expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA, subject.merge_request_diff.head_commit_sha])).to be_truthy + end + + context 'unpersisted merge request' do + let(:new_mr) { build(:merge_request) } + + it 'returns false' do + expect(new_mr.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey + end + + it 'returns true' do + expect(new_mr.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy + end + end + end + describe '#can_allow_collaboration?' do let(:target_project) { create(:project, :public) } let(:source_project) { fork_project(target_project) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 8913644a3ce..2db42fe802a 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -562,6 +562,17 @@ describe Namespace do it { expect(group.all_projects.to_a).to match_array([project2, project1]) } end + describe '#all_pipelines' do + let(:group) { create(:group) } + let(:child) { create(:group, parent: group) } + let!(:project1) { create(:project_empty_repo, namespace: group) } + let!(:project2) { create(:project_empty_repo, namespace: child) } + let!(:pipeline1) { create(:ci_empty_pipeline, project: project1) } + let!(:pipeline2) { create(:ci_empty_pipeline, project: project2) } + + it { expect(group.all_pipelines.to_a).to match_array([pipeline1, pipeline2]) } + end + describe '#share_with_group_lock with subgroups', :nested_groups do context 'when creating a subgroup' do let(:subgroup) { create(:group, parent: root_group )} diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb index 919a7526803..e100af7ddc7 100644 --- a/spec/models/postgresql/replication_slot_spec.rb +++ b/spec/models/postgresql/replication_slot_spec.rb @@ -3,7 +3,27 @@ require 'spec_helper' describe Postgresql::ReplicationSlot, :postgresql do + describe '.in_use?' do + it 'returns true when replication slots are present' do + expect(described_class).to receive(:exists?).and_return(true) + expect(described_class.in_use?).to be_truthy + end + + it 'returns false when replication slots are not present' do + expect(described_class.in_use?).to be_falsey + end + + it 'returns false if the existence check is invalid' do + expect(described_class).to receive(:exists?).and_raise(ActiveRecord::StatementInvalid.new('PG::FeatureNotSupported')) + expect(described_class.in_use?).to be_falsey + end + end + describe '.lag_too_great?' do + before do + expect(described_class).to receive(:in_use?).and_return(true) + end + it 'returns true when replication lag is too great' do expect(described_class) .to receive(:pluck) diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 68ab9fd08ec..9c27357ffaf 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -253,7 +253,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end end - describe '#predefined_variables' do + describe '#predefined_variable' do let(:kubeconfig) do config_file = expand_fixture_path('config/kubeconfig.yml') config = YAML.load(File.read(config_file)) @@ -274,7 +274,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do shared_examples 'setting variables' do it 'sets the variables' do - expect(subject.predefined_variables).to include( + expect(subject.predefined_variables(project: project)).to include( { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }, { key: 'KUBE_TOKEN', value: 'token', public: false }, { key: 'KUBE_NAMESPACE', value: namespace, public: true }, @@ -301,7 +301,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do it_behaves_like 'setting variables' it 'sets the KUBE_NAMESPACE' do - kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' } + kube_namespace = subject.predefined_variables(project: project).find { |h| h[:key] == 'KUBE_NAMESPACE' } expect(kube_namespace).not_to be_nil expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index be2aff73c0a..471f19f9b7c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -8,6 +8,7 @@ describe Project do it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:namespace) } it { is_expected.to belong_to(:creator).class_name('User') } + it { is_expected.to belong_to(:pool_repository) } it { is_expected.to have_many(:users) } it { is_expected.to have_many(:services) } it { is_expected.to have_many(:events) } @@ -2405,12 +2406,24 @@ describe Project do it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' end - context 'when user configured kubernetes from CI/CD > Clusters' do + context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' end + + context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace) } + let!(:cluster) { kubernetes_namespace.cluster } + let(:project) { kubernetes_namespace.project } + + it 'should return token from kubernetes namespace' do + expect(project.deployment_variables).to include( + { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false } + ) + end + end end end @@ -3963,6 +3976,40 @@ describe Project do end end + describe '.deployments' do + subject { project.deployments } + + let(:project) { create(:project) } + + before do + allow_any_instance_of(Deployment).to receive(:create_ref) + end + + context 'when there is a deployment record with created status' do + let(:deployment) { create(:deployment, :created, project: project) } + + it 'does not return the record' do + is_expected.to be_empty + end + end + + context 'when there is a deployment record with running status' do + let(:deployment) { create(:deployment, :running, project: project) } + + it 'does not return the record' do + is_expected.to be_empty + end + end + + context 'when there is a deployment record with success status' do + let(:deployment) { create(:deployment, :success, project: project) } + + it 'returns the record' do + is_expected.to eq([deployment]) + end + end + end + describe '#snippets_visible?' do it 'returns true when a logged in user can read snippets' do project = create(:project, :public) diff --git a/spec/models/shard_spec.rb b/spec/models/shard_spec.rb new file mode 100644 index 00000000000..83104711b55 --- /dev/null +++ b/spec/models/shard_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literals: true +require 'spec_helper' + +describe Shard do + describe '.populate!' do + it 'creates shards based on the config file' do + expect(described_class.all).to be_empty + + stub_storage_settings(foo: {}, bar: {}, baz: {}) + + described_class.populate! + + expect(described_class.all.map(&:name)).to match_array(%w[default foo bar baz]) + end + end + + describe '.by_name' do + let(:default_shard) { described_class.find_by(name: 'default') } + + before do + described_class.populate! + end + + it 'returns an existing shard' do + expect(described_class.by_name('default')).to eq(default_shard) + end + + it 'creates a new shard' do + result = described_class.by_name('foo') + + expect(result).not_to eq(default_shard) + expect(result.name).to eq('foo') + end + + it 'retries if creation races' do + expect(described_class) + .to receive(:find_or_create_by) + .with(name: 'default') + .and_raise(ActiveRecord::RecordNotUnique, 'fail') + .once + + expect(described_class) + .to receive(:find_or_create_by) + .with(name: 'default') + .and_call_original + + expect(described_class.by_name('default')).to eq(default_shard) + end + end +end diff --git a/spec/models/user_preference_spec.rb b/spec/models/user_preference_spec.rb index 64d9d9a78b4..2898613545c 100644 --- a/spec/models/user_preference_spec.rb +++ b/spec/models/user_preference_spec.rb @@ -6,22 +6,43 @@ describe UserPreference do describe '#set_notes_filter' do let(:issuable) { build_stubbed(:issue) } let(:user_preference) { create(:user_preference) } - let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] } - it 'returns updated discussion filter' do - filter_name = - user_preference.set_notes_filter(only_comments, issuable) + shared_examples 'setting system notes' do + it 'returns updated discussion filter' do + filter_name = + user_preference.set_notes_filter(filter, issuable) + + expect(filter_name).to eq(filter) + end + + it 'updates discussion filter for issuable class' do + user_preference.set_notes_filter(filter, issuable) + + expect(user_preference.reload.issue_notes_filter).to eq(filter) + end + end + + context 'when filter is set to all notes' do + let(:filter) { described_class::NOTES_FILTERS[:all_notes] } + + it_behaves_like 'setting system notes' + end + + context 'when filter is set to only comments' do + let(:filter) { described_class::NOTES_FILTERS[:only_comments] } - expect(filter_name).to eq(only_comments) + it_behaves_like 'setting system notes' end - it 'updates discussion filter for issuable class' do - user_preference.set_notes_filter(only_comments, issuable) + context 'when filter is set to only activity' do + let(:filter) { described_class::NOTES_FILTERS[:only_activity] } - expect(user_preference.reload.issue_notes_filter).to eq(only_comments) + it_behaves_like 'setting system notes' end context 'when notes_filter parameter is invalid' do + let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] } + it 'returns the current notes filter' do user_preference.set_notes_filter(only_comments, issuable) diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index d7992f0a4a9..676835b3880 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -267,7 +267,7 @@ describe Ci::BuildPresenter do let(:build) { create(:ci_build, :failed, :script_failure) } context 'when is a script or missing dependency failure' do - let(:failure_reasons) { %w(script_failure missing_dependency_failure) } + let(:failure_reasons) { %w(script_failure missing_dependency_failure archived_failure) } it 'should return false' do failure_reasons.each do |failure_reason| diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb new file mode 100644 index 00000000000..4f4ae5e07c5 --- /dev/null +++ b/spec/presenters/clusterable_presenter_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClusterablePresenter do + include Gitlab::Routing.url_helpers + + describe '.fabricate' do + let(:project) { create(:project) } + + subject { described_class.fabricate(project) } + + it 'creates an object from a descendant presenter' do + expect(subject).to be_kind_of(ProjectClusterablePresenter) + end + end +end diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index e96dbfb73c0..7af181f37d5 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Clusters::ClusterPresenter do - let(:cluster) { create(:cluster, :provided_by_gcp) } + include Gitlab::Routing.url_helpers + + let(:cluster) { create(:cluster, :provided_by_gcp, :project) } subject(:presenter) do described_class.new(cluster) @@ -71,4 +73,14 @@ describe Clusters::ClusterPresenter do it { is_expected.to eq(false) } end end + + describe '#show_path' do + subject { described_class.new(cluster).show_path } + + context 'project_type cluster' do + let(:project) { cluster.project } + + it { is_expected.to eq(project_cluster_path(project, cluster)) } + end + end end diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb new file mode 100644 index 00000000000..c50d90ae1e8 --- /dev/null +++ b/spec/presenters/project_clusterable_presenter_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectClusterablePresenter do + include Gitlab::Routing.url_helpers + + let(:presenter) { described_class.new(project) } + let(:project) { create(:project) } + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + + describe '#can_create_cluster?' do + let(:user) { create(:user) } + + subject { presenter.can_create_cluster? } + + before do + allow(presenter).to receive(:current_user).and_return(user) + end + + context 'when user can create' do + before do + project.add_maintainer(user) + end + + it { is_expected.to be_truthy } + end + + context 'when user cannot create' do + it { is_expected.to be_falsey } + end + end + + describe '#index_path' do + subject { presenter.index_path } + + it { is_expected.to eq(project_clusters_path(project)) } + end + + describe '#new_path' do + subject { presenter.new_path } + + it { is_expected.to eq(new_project_cluster_path(project)) } + end + + describe '#create_user_clusters_path' do + subject { presenter.create_user_clusters_path } + + it { is_expected.to eq(create_user_project_clusters_path(project)) } + end + + describe '#create_gcp_clusters_path' do + subject { presenter.create_gcp_clusters_path } + + it { is_expected.to eq(create_gcp_project_clusters_path(project)) } + end + + describe '#cluster_status_cluster_path' do + subject { presenter.cluster_status_cluster_path(cluster) } + + it { is_expected.to eq(cluster_status_project_cluster_path(project, cluster)) } + end + + describe '#install_applications_cluster_path' do + let(:application) { :helm } + + subject { presenter.install_applications_cluster_path(cluster, application) } + + it { is_expected.to eq(install_applications_project_cluster_path(project, cluster, application)) } + end + + describe '#cluster_path' do + subject { presenter.cluster_path(cluster) } + + it { is_expected.to eq(project_cluster_path(project, cluster)) } + end +end diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 61ae053cea7..3dac7225b7a 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -10,9 +10,9 @@ describe API::Deployments do describe 'GET /projects/:id/deployments' do let(:project) { create(:project) } - let!(:deployment_1) { create(:deployment, project: project, iid: 11, ref: 'master', created_at: Time.now) } - let!(:deployment_2) { create(:deployment, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago) } - let!(:deployment_3) { create(:deployment, project: project, iid: 8, ref: 'feature', created_at: 2.days.ago) } + let!(:deployment_1) { create(:deployment, :success, project: project, iid: 11, ref: 'master', created_at: Time.now) } + let!(:deployment_2) { create(:deployment, :success, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago) } + let!(:deployment_3) { create(:deployment, :success, project: project, iid: 8, ref: 'patch', created_at: 2.days.ago) } context 'as member of the project' do it 'returns projects deployments sorted by id asc' do @@ -53,8 +53,8 @@ describe API::Deployments do 'id' | 'desc' | [:deployment_3, :deployment_2, :deployment_1] 'iid' | 'asc' | [:deployment_3, :deployment_1, :deployment_2] 'iid' | 'desc' | [:deployment_2, :deployment_1, :deployment_3] - 'ref' | 'asc' | [:deployment_2, :deployment_3, :deployment_1] - 'ref' | 'desc' | [:deployment_1, :deployment_2, :deployment_3] + 'ref' | 'asc' | [:deployment_2, :deployment_1, :deployment_3] + 'ref' | 'desc' | [:deployment_3, :deployment_1, :deployment_2] end with_them do @@ -76,7 +76,7 @@ describe API::Deployments do describe 'GET /projects/:id/deployments/:deployment_id' do let(:project) { deployment.environment.project } - let!(:deployment) { create(:deployment) } + let!(:deployment) { create(:deployment, :success) } context 'as a member of the project' do it 'returns the projects deployment' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 5dbe967e4fe..3d532dd83c7 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1801,6 +1801,74 @@ describe API::Issues do end end + describe 'GET :id/issues/:issue_iid/related_merge_requests' do + def get_related_merge_requests(project_id, issue_iid, user = nil) + get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user) + end + + def create_referencing_mr(user, project, issue) + attributes = { + author: user, + source_project: project, + target_project: project, + source_branch: "master", + target_branch: "test", + description: "See #{issue.to_reference}" + } + create(:merge_request, attributes).tap do |merge_request| + create(:note, :system, project: project, noteable: issue, author: user, note: merge_request.to_reference(full: true)) + end + end + + let!(:related_mr) { create_referencing_mr(user, project, issue) } + + context 'when unauthenticated' do + it 'return list of referenced merge requests from issue' do + get_related_merge_requests(project.id, issue.iid) + + expect_paginated_array_response(size: 1) + end + + it 'renders 404 if project is not visible' do + private_project = create(:project, :private) + private_issue = create(:issue, project: private_project) + create_referencing_mr(user, private_project, private_issue) + + get_related_merge_requests(private_project.id, private_issue.iid) + + expect(response).to have_gitlab_http_status(404) + end + end + + it 'returns merge requests that mentioned a issue' do + create(:merge_request, + :simple, + author: user, + source_project: project, + target_project: project, + description: "Some description") + + get_related_merge_requests(project.id, issue.iid, user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(related_mr.id) + end + + context 'no merge request mentioned a issue' do + it 'returns empty array' do + get_related_merge_requests(project.id, closed_issue.iid, user) + + expect_paginated_array_response(size: 0) + end + end + + it "returns 404 when issue doesn't exists" do + get_related_merge_requests(project.id, 999999, user) + + expect(response).to have_gitlab_http_status(404) + end + end + describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) } diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb index 9e2bee2ee60..ea88951ebc6 100644 --- a/spec/serializers/build_action_entity_spec.rb +++ b/spec/serializers/build_action_entity_spec.rb @@ -26,6 +26,10 @@ describe BuildActionEntity do context 'when job is scheduled' do let(:job) { create(:ci_build, :scheduled) } + it 'returns scheduled' do + expect(subject[:scheduled]).to be_truthy + end + it 'returns scheduled_at' do expect(subject[:scheduled_at]).to eq(job.scheduled_at) end diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb index 522c92ce295..8793a762f9d 100644 --- a/spec/serializers/deployment_entity_spec.rb +++ b/spec/serializers/deployment_entity_spec.rb @@ -22,4 +22,26 @@ describe DeploymentEntity do it 'exposes creation date' do expect(subject).to include(:created_at) end + + describe 'scheduled_actions' do + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_pipeline, project: project, user: user) } + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + let(:deployment) { create(:deployment, deployable: build) } + + context 'when the same pipeline has a scheduled action' do + let(:other_build) { create(:ci_build, :schedulable, :success, pipeline: pipeline, name: 'other build') } + let!(:other_deployment) { create(:deployment, deployable: other_build) } + + it 'returns other scheduled actions' do + expect(subject[:scheduled_actions][0][:name]).to eq 'other build' + end + end + + context 'when the same pipeline does not have a scheduled action' do + it 'does not return other actions' do + expect(subject[:scheduled_actions]).to be_empty + end + end + end end diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 0f0ab5ac796..87493a28d1f 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -14,7 +14,8 @@ describe EnvironmentSerializer do let(:project) { create(:project, :repository) } let(:deployable) { create(:ci_build) } let(:deployment) do - create(:deployment, deployable: deployable, + create(:deployment, :success, + deployable: deployable, user: user, project: project, sha: project.commit.id) diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb index 1b4d8b70aa6..962ec919092 100644 --- a/spec/serializers/environment_status_entity_spec.rb +++ b/spec/serializers/environment_status_entity_spec.rb @@ -4,8 +4,8 @@ describe EnvironmentStatusEntity do let(:user) { create(:user) } let(:request) { double('request') } - let(:deployment) { create(:deployment, :review_app) } - let(:environment) { deployment.environment} + let(:deployment) { create(:deployment, :succeed, :review_app) } + let(:environment) { deployment.environment } let(:project) { deployment.project } let(:merge_request) { create(:merge_request, :deployed_review_app, deployment: deployment) } diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb index 5fc27da4906..851b41a7f7e 100644 --- a/spec/serializers/job_entity_spec.rb +++ b/spec/serializers/job_entity_spec.rb @@ -117,6 +117,7 @@ describe JobEntity do end it 'contains scheduled_at' do + expect(subject[:scheduled]).to be_truthy expect(subject[:scheduled_at]).to eq(job.scheduled_at) end end diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb index 9a53b32394d..704685417bb 100644 --- a/spec/services/ci/process_build_service_spec.rb +++ b/spec/services/ci/process_build_service_spec.rb @@ -98,47 +98,19 @@ describe Ci::ProcessBuildService, '#execute' do let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) } - context 'when ci_enable_scheduled_build is enabled' do - before do - stub_feature_flags(ci_enable_scheduled_build: true) - end - - context 'when current status is success' do - let(:current_status) { 'success' } - - it 'changes the build status' do - expect { subject }.to change { build.status }.to('scheduled') - end - end - - context 'when current status is failed' do - let(:current_status) { 'failed' } + context 'when current status is success' do + let(:current_status) { 'success' } - it 'does not change the build status' do - expect { subject }.to change { build.status }.to('skipped') - end + it 'changes the build status' do + expect { subject }.to change { build.status }.to('scheduled') end end - context 'when ci_enable_scheduled_build is disabled' do - before do - stub_feature_flags(ci_enable_scheduled_build: false) - end - - context 'when current status is success' do - let(:current_status) { 'success' } - - it 'changes the build status' do - expect { subject }.to change { build.status }.to('manual') - end - end - - context 'when current status is failed' do - let(:current_status) { 'failed' } + context 'when current status is failed' do + let(:current_status) { 'failed' } - it 'does not change the build status' do - expect { subject }.to change { build.status }.to('skipped') - end + it 'does not change the build status' do + expect { subject }.to change { build.status }.to('skipped') end end end diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index a6565709641..56e2a405bcd 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -478,6 +478,20 @@ module Ci it_behaves_like 'validation is not active' end end + + context 'when build is degenerated' do + let!(:pending_job) { create(:ci_build, :pending, :degenerated, pipeline: pipeline) } + + subject { execute(specific_runner, {}) } + + it 'does not pick the build and drops the build' do + expect(subject).to be_nil + + pending_job.reload + expect(pending_job).to be_failed + expect(pending_job).to be_archived_failure + end + end end describe '#register_success' do diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 368abded448..e779675744c 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -32,7 +32,7 @@ describe Ci::RetryBuildService do IGNORE_ACCESSORS = %i[type lock_version target_url base_tags trace_sections - commit_id deployments erased_by_id last_deployment project_id + commit_id deployment erased_by_id project_id runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason artifacts_file_store artifacts_metadata_store diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb index 2c921dac238..be2aad33ef4 100644 --- a/spec/services/ci/run_scheduled_build_service_spec.rb +++ b/spec/services/ci/run_scheduled_build_service_spec.rb @@ -7,10 +7,6 @@ describe Ci::RunScheduledBuildService do subject { described_class.new(project, user).execute(build) } - before do - stub_feature_flags(ci_enable_scheduled_build: true) - end - context 'when user can update build' do before do project.add_developer(user) diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb index 3959295c13e..274880f2c49 100644 --- a/spec/services/clusters/create_service_spec.rb +++ b/spec/services/clusters/create_service_spec.rb @@ -5,18 +5,43 @@ describe Clusters::CreateService do let(:project) { create(:project) } let(:user) { create(:user) } - subject { described_class.new(user, params).execute(project: project, access_token: access_token) } + subject { described_class.new(user, params).execute(access_token: access_token) } context 'when provider is gcp' do context 'when project has no clusters' do context 'when correct params' do - include_context 'valid cluster create params' + let(:params) do + { + name: 'test-cluster', + provider_type: :gcp, + provider_gcp_attributes: { + gcp_project_id: 'gcp-project', + zone: 'us-central1-a', + num_nodes: 1, + machine_type: 'machine_type-a', + legacy_abac: 'true' + }, + clusterable: project + } + end include_examples 'create cluster service success' end context 'when invalid params' do - include_context 'invalid cluster create params' + let(:params) do + { + name: 'test-cluster', + provider_type: :gcp, + provider_gcp_attributes: { + gcp_project_id: '!!!!!!!', + zone: 'us-central1-a', + num_nodes: 1, + machine_type: 'machine_type-a' + }, + clusterable: project + } + end include_examples 'create cluster service error' end diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb index 303d45495ef..7fbb6cf2cf5 100644 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb @@ -1,156 +1,176 @@ +# frozen_string_literal: true + require 'spec_helper' -describe Clusters::Gcp::FinalizeCreationService do +describe Clusters::Gcp::FinalizeCreationService, '#execute' do include GoogleApi::CloudPlatformHelpers include KubernetesHelpers - describe '#execute' do - let(:cluster) { create(:cluster, :project, :providing_by_gcp) } - let(:provider) { cluster.provider } - let(:platform) { cluster.platform } - let(:gcp_project_id) { provider.gcp_project_id } - let(:zone) { provider.zone } - let(:cluster_name) { cluster.name } + let(:cluster) { create(:cluster, :project, :providing_by_gcp) } + let(:provider) { cluster.provider } + let(:platform) { cluster.platform } + let(:endpoint) { '111.111.111.111' } + let(:api_url) { 'https://' + endpoint } + let(:username) { 'sample-username' } + let(:password) { 'sample-password' } + let(:secret_name) { 'gitlab-token' } + let(:token) { 'sample-token' } + let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" } - subject { described_class.new.execute(provider) } + subject { described_class.new.execute(provider) } - shared_examples 'success' do - it 'configures provider and kubernetes' do - subject + shared_examples 'success' do + it 'configures provider and kubernetes' do + subject - expect(provider).to be_created - end + expect(provider).to be_created end - shared_examples 'error' do - it 'sets an error to provider object' do - subject + it 'properly configures database models' do + subject - expect(provider.reload).to be_errored - end + cluster.reload + + expect(provider.endpoint).to eq(endpoint) + expect(platform.api_url).to eq(api_url) + expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert)) + expect(platform.username).to eq(username) + expect(platform.password).to eq(password) + expect(platform.token).to eq(token) + end + + it 'creates kubernetes namespace model' do + subject + + kubernetes_namespace = cluster.reload.kubernetes_namespace + expect(kubernetes_namespace).to be_persisted + expect(kubernetes_namespace.namespace).to eq(namespace) + expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") + expect(kubernetes_namespace.service_account_token).to be_present end + end + + shared_examples 'error' do + it 'sets an error to provider object' do + subject - context 'when succeeded to fetch gke cluster info' do - let(:endpoint) { '111.111.111.111' } - let(:api_url) { 'https://' + endpoint } - let(:username) { 'sample-username' } - let(:password) { 'sample-password' } - let(:secret_name) { 'gitlab-token' } + expect(provider.reload).to be_errored + end + end + shared_examples 'kubernetes information not successfully fetched' do + context 'when failed to fetch gke cluster info' do before do - stub_cloud_platform_get_zone_cluster( - gcp_project_id, zone, cluster_name, - { - endpoint: endpoint, - username: username, - password: password - } - ) + stub_cloud_platform_get_zone_cluster_error(provider.gcp_project_id, provider.zone, cluster.name) end - context 'service account and token created' do - before do - stub_kubeclient_discover(api_url) - stub_kubeclient_create_service_account(api_url) - stub_kubeclient_create_secret(api_url) - end - - shared_context 'kubernetes token successfully fetched' do - let(:token) { 'sample-token' } - - before do - stub_kubeclient_get_secret( - api_url, - { - metadata_name: secret_name, - token: Base64.encode64(token) - } ) - end - end - - context 'provider legacy_abac is enabled' do - include_context 'kubernetes token successfully fetched' - - it_behaves_like 'success' - - it 'properly configures database models' do - subject - - cluster.reload - - expect(provider.endpoint).to eq(endpoint) - expect(platform.api_url).to eq(api_url) - expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert)) - expect(platform.username).to eq(username) - expect(platform.password).to eq(password) - expect(platform).to be_abac - expect(platform.authorization_type).to eq('abac') - expect(platform.token).to eq(token) - end - end - - context 'provider legacy_abac is disabled' do - before do - provider.legacy_abac = false - end - - include_context 'kubernetes token successfully fetched' - - context 'cluster role binding created' do - before do - stub_kubeclient_create_cluster_role_binding(api_url) - end - - it_behaves_like 'success' - - it 'properly configures database models' do - subject - - cluster.reload - - expect(provider.endpoint).to eq(endpoint) - expect(platform.api_url).to eq(api_url) - expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert)) - expect(platform.username).to eq(username) - expect(platform.password).to eq(password) - expect(platform).to be_rbac - expect(platform.token).to eq(token) - end - end - end - - context 'when token is empty' do - before do - stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name) - end - - it_behaves_like 'error' - end - - context 'when failed to fetch kubernetes token' do - before do - stub_kubeclient_get_secret_error(api_url, secret_name) - end - - it_behaves_like 'error' - end - - context 'when service account fails to create' do - before do - stub_kubeclient_create_service_account_error(api_url) - end - - it_behaves_like 'error' - end + it_behaves_like 'error' + end + + context 'when token is empty' do + let(:token) { '' } + + it_behaves_like 'error' + end + + context 'when failed to fetch kubernetes token' do + before do + stub_kubeclient_get_secret_error(api_url, secret_name, namespace: 'default') end + + it_behaves_like 'error' end - context 'when failed to fetch gke cluster info' do + context 'when service account fails to create' do before do - stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name) + stub_kubeclient_create_service_account_error(api_url, namespace: 'default') end it_behaves_like 'error' end end + + shared_context 'kubernetes information successfully fetched' do + before do + stub_cloud_platform_get_zone_cluster( + provider.gcp_project_id, provider.zone, cluster.name, + { + endpoint: endpoint, + username: username, + password: password + } + ) + + stub_kubeclient_discover(api_url) + stub_kubeclient_get_namespace(api_url) + stub_kubeclient_create_namespace(api_url) + stub_kubeclient_create_service_account(api_url) + stub_kubeclient_create_secret(api_url) + + stub_kubeclient_get_secret( + api_url, + { + metadata_name: secret_name, + token: Base64.encode64(token), + namespace: 'default' + } + ) + + stub_kubeclient_get_namespace(api_url, namespace: namespace) + stub_kubeclient_create_service_account(api_url, namespace: namespace) + stub_kubeclient_create_secret(api_url, namespace: namespace) + + stub_kubeclient_get_secret( + api_url, + { + metadata_name: "#{namespace}-token", + token: Base64.encode64(token), + namespace: namespace + } + ) + end + end + + context 'With a legacy ABAC cluster' do + before do + provider.legacy_abac = true + end + + include_context 'kubernetes information successfully fetched' + + it_behaves_like 'success' + + it 'uses ABAC authorization type' do + subject + cluster.reload + + expect(platform).to be_abac + expect(platform.authorization_type).to eq('abac') + end + + it_behaves_like 'kubernetes information not successfully fetched' + end + + context 'With an RBAC cluster' do + before do + provider.legacy_abac = false + + stub_kubeclient_create_cluster_role_binding(api_url) + stub_kubeclient_create_role_binding(api_url, namespace: namespace) + end + + include_context 'kubernetes information successfully fetched' + + it_behaves_like 'success' + + it 'uses RBAC authorization type' do + subject + cluster.reload + + expect(platform).to be_rbac + expect(platform.authorization_type).to eq('rbac') + end + + it_behaves_like 'kubernetes information not successfully fetched' + end end diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb new file mode 100644 index 00000000000..fc922218ad0 --- /dev/null +++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do + include KubernetesHelpers + + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:platform) { cluster.platform } + let(:api_url) { 'https://kubernetes.example.com' } + let(:project) { cluster.project } + let(:cluster_project) { cluster.cluster_project } + + subject do + described_class.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end + + shared_context 'kubernetes requests' do + before do + stub_kubeclient_discover(api_url) + stub_kubeclient_get_namespace(api_url) + stub_kubeclient_create_service_account(api_url) + stub_kubeclient_create_secret(api_url) + + stub_kubeclient_get_namespace(api_url, namespace: namespace) + stub_kubeclient_create_service_account(api_url, namespace: namespace) + stub_kubeclient_create_secret(api_url, namespace: namespace) + + stub_kubeclient_get_secret( + api_url, + { + metadata_name: "#{namespace}-token", + token: Base64.encode64('sample-token'), + namespace: namespace + } + ) + end + end + + context 'when kubernetes namespace is not persisted' do + let(:namespace) { "#{project.path}-#{project.id}" } + + let(:kubernetes_namespace) do + build(:cluster_kubernetes_namespace, + cluster: cluster, + project: cluster_project.project, + cluster_project: cluster_project) + end + + include_context 'kubernetes requests' + + it 'creates a Clusters::KubernetesNamespace' do + expect do + subject + end.to change(Clusters::KubernetesNamespace, :count).by(1) + end + + it 'creates project service account' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once + + subject + end + + it 'configures kubernetes token' do + subject + + kubernetes_namespace.reload + expect(kubernetes_namespace.namespace).to eq(namespace) + expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") + expect(kubernetes_namespace.encrypted_service_account_token).to be_present + end + end + + context 'when there is a Kubernetes Namespace associated' do + let(:namespace) { 'new-namespace' } + + let(:kubernetes_namespace) do + create(:cluster_kubernetes_namespace, + cluster: cluster, + project: cluster_project.project, + cluster_project: cluster_project) + end + + include_context 'kubernetes requests' + + before do + platform.update_column(:namespace, 'new-namespace') + end + + it 'does not create any Clusters::KubernetesNamespace' do + subject + + expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace) + end + + it 'creates project service account' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once + + subject + end + + it 'updates Clusters::KubernetesNamespace' do + subject + + kubernetes_namespace.reload + + expect(kubernetes_namespace.namespace).to eq(namespace) + expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account") + expect(kubernetes_namespace.encrypted_service_account_token).to be_present + end + end +end diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb index b096f1fa4fb..588edff85d4 100644 --- a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb @@ -1,94 +1,165 @@ # frozen_string_literal: true - require 'spec_helper' describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do include KubernetesHelpers - let(:service) { described_class.new(kubeclient, rbac: rbac) } + let(:api_url) { 'http://111.111.111.111' } + let(:platform_kubernetes) { cluster.platform_kubernetes } + let(:cluster_project) { cluster.cluster_project } + let(:project) { cluster_project.project } + let(:cluster) do + create(:cluster, + :project, :provided_by_gcp, + platform_kubernetes: create(:cluster_platform_kubernetes, :configured)) + end + + let(:kubeclient) do + Gitlab::Kubernetes::KubeClient.new( + api_url, + auth_options: { username: 'admin', password: 'xxx' } + ) + end - describe '#execute' do - let(:rbac) { false } - let(:api_url) { 'http://111.111.111.111' } - let(:username) { 'admin' } - let(:password) { 'xxx' } + shared_examples 'creates service account and token' do + it 'creates a kubernetes service account' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with( + body: hash_including( + kind: 'ServiceAccount', + metadata: { name: service_account_name, namespace: namespace } + ) + ) + end - let(:kubeclient) do - Gitlab::Kubernetes::KubeClient.new( - api_url, - auth_options: { username: username, password: password } + it 'creates a kubernetes secret' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( + body: hash_including( + kind: 'Secret', + metadata: { + name: token_name, + namespace: namespace, + annotations: { + 'kubernetes.io/service-account.name': service_account_name + } + }, + type: 'kubernetes.io/service-account-token' + ) ) end + end + + before do + stub_kubeclient_discover(api_url) + stub_kubeclient_get_namespace(api_url, namespace: namespace) + stub_kubeclient_create_service_account(api_url, namespace: namespace ) + stub_kubeclient_create_secret(api_url, namespace: namespace) + end + + describe '.gitlab_creator' do + let(:namespace) { 'default' } + let(:service_account_name) { 'gitlab' } + let(:token_name) { 'gitlab-token' } + + subject { described_class.gitlab_creator(kubeclient, rbac: rbac).execute } + + context 'with ABAC cluster' do + let(:rbac) { false } + + it_behaves_like 'creates service account and token' + end - subject { service.execute } + context 'with RBAC cluster' do + let(:rbac) { true } - context 'when params are correct' do before do - stub_kubeclient_discover(api_url) - stub_kubeclient_create_service_account(api_url) - stub_kubeclient_create_secret(api_url) - end + cluster.platform_kubernetes.rbac! - shared_examples 'creates service account and token' do - it 'creates a kubernetes service account' do - subject + stub_kubeclient_create_cluster_role_binding(api_url) + end - expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/serviceaccounts').with( - body: hash_including( - kind: 'ServiceAccount', - metadata: { name: 'gitlab', namespace: 'default' } - ) - ) - end - - it 'creates a kubernetes secret of type ServiceAccountToken' do - subject - - expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with( - body: hash_including( - kind: 'Secret', - metadata: { - name: 'gitlab-token', - namespace: 'default', - annotations: { - 'kubernetes.io/service-account.name': 'gitlab' - } - }, - type: 'kubernetes.io/service-account-token' - ) + it_behaves_like 'creates service account and token' + + it 'should create a cluster role binding with cluster-admin access' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with( + body: hash_including( + kind: 'ClusterRoleBinding', + metadata: { name: 'gitlab-admin' }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'ClusterRole', + name: 'cluster-admin' + }, + subjects: [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] ) - end + ) end + end + end + + describe '.namespace_creator' do + let(:namespace) { "#{project.path}-#{project.id}" } + let(:service_account_name) { "#{namespace}-service-account" } + let(:token_name) { "#{namespace}-token" } + + subject do + described_class.namespace_creator( + kubeclient, + service_account_name: service_account_name, + service_account_namespace: namespace, + rbac: rbac + ).execute + end + + context 'with ABAC cluster' do + let(:rbac) { false } + + it_behaves_like 'creates service account and token' + end + + context 'With RBAC enabled cluster' do + let(:rbac) { true } + + before do + cluster.platform_kubernetes.rbac! - context 'abac enabled cluster' do - it_behaves_like 'creates service account and token' + stub_kubeclient_create_role_binding(api_url, namespace: namespace) end - context 'rbac enabled cluster' do - let(:rbac) { true } - - before do - stub_kubeclient_create_cluster_role_binding(api_url) - end - - it_behaves_like 'creates service account and token' - - it 'creates a kubernetes cluster role binding' do - subject - - expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with( - body: hash_including( - kind: 'ClusterRoleBinding', - metadata: { name: 'gitlab-admin' }, - roleRef: { - apiGroup: 'rbac.authorization.k8s.io', - kind: 'ClusterRole', - name: 'cluster-admin' - }, - subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }] - ) + it_behaves_like 'creates service account and token' + + it 'creates a namespaced role binding with edit access' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with( + body: hash_including( + kind: 'RoleBinding', + metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" }, + roleRef: { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'ClusterRole', + name: 'edit' + }, + subjects: [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] ) - end + ) end end end diff --git a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb index 2355827fa5a..4d1a6bb7b3a 100644 --- a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb @@ -1,56 +1,48 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do + include KubernetesHelpers + describe '#execute' do let(:api_url) { 'http://111.111.111.111' } - let(:username) { 'admin' } - let(:password) { 'xxx' } + let(:namespace) { 'my-namespace' } + let(:service_account_token_name) { 'gitlab-token' } let(:kubeclient) do Gitlab::Kubernetes::KubeClient.new( api_url, - auth_options: { username: username, password: password } + auth_options: { username: 'admin', password: 'xxx' } ) end - subject { described_class.new(kubeclient).execute } + subject { described_class.new(kubeclient, service_account_token_name, namespace).execute } context 'when params correct' do let(:decoded_token) { 'xxx.token.xxx' } let(:token) { Base64.encode64(decoded_token) } - let(:secret_json) do - { - 'metadata': { - name: 'gitlab-token' - }, - 'data': { - 'token': token - } - } - end - - before do - allow_any_instance_of(Kubeclient::Client) - .to receive(:get_secret).and_return(secret_json) - end - context 'when gitlab-token exists' do - let(:metadata_name) { 'gitlab-token' } + before do + stub_kubeclient_discover(api_url) + stub_kubeclient_get_secret( + api_url, + { + metadata_name: service_account_token_name, + namespace: namespace, + token: token + } + ) + end it { is_expected.to eq(decoded_token) } end context 'when gitlab-token does not exist' do - let(:secret_json) { {} } - - it { is_expected.to be_nil } - end - - context 'when token is nil' do - let(:token) { nil } + before do + allow(kubeclient).to receive(:get_secret).and_raise(Kubeclient::HttpError.new(404, 'Not found', nil)) + end it { is_expected.to be_nil } end diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index dcd75b6912d..a1b20c61116 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Clusters::UpdateService do + include KubernetesHelpers + describe '#execute' do subject { described_class.new(cluster.user, params).execute(cluster) } @@ -34,6 +36,11 @@ describe Clusters::UpdateService do } end + before do + allow(ClusterPlatformConfigureWorker).to receive(:perform_async) + stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') + end + it 'updates namespace' do is_expected.to eq(true) expect(cluster.platform.namespace).to eq('custom-namespace') diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb deleted file mode 100644 index b9bfbb11511..00000000000 --- a/spec/services/create_deployment_service_spec.rb +++ /dev/null @@ -1,335 +0,0 @@ -require 'spec_helper' - -describe CreateDeploymentService do - let(:user) { create(:user) } - let(:options) { nil } - - let(:job) do - create(:ci_build, - ref: 'master', - tag: false, - environment: 'production', - options: { environment: options }) - end - - let(:project) { job.project } - - let!(:environment) do - create(:environment, project: project, name: 'production') - end - - let(:service) { described_class.new(job) } - - before do - allow_any_instance_of(Deployment).to receive(:create_ref) - end - - describe '#execute' do - subject { service.execute } - - context 'when environment exists' do - it 'creates a deployment' do - expect(subject).to be_persisted - end - end - - context 'when environment does not exist' do - let(:environment) {} - - it 'does not create a deployment' do - expect do - expect(subject).to be_nil - end.not_to change { Deployment.count } - end - end - - context 'when start action is defined' do - let(:options) { { action: 'start' } } - - context 'and environment is stopped' do - before do - environment.stop - end - - it 'makes environment available' do - subject - - expect(environment.reload).to be_available - end - - it 'creates a deployment' do - expect(subject).to be_persisted - end - end - end - - context 'when stop action is defined' do - let(:options) { { action: 'stop' } } - - context 'and environment is available' do - before do - environment.start - end - - it 'makes environment stopped' do - subject - - expect(environment.reload).to be_stopped - end - - it 'does not create a deployment' do - expect(subject).to be_nil - end - end - end - - context 'when variables are used' do - let(:options) do - { name: 'review-apps/$CI_COMMIT_REF_NAME', - url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com' } - end - - before do - environment.update(name: 'review-apps/master') - job.update(environment: 'review-apps/$CI_COMMIT_REF_NAME') - end - - it 'creates a new deployment' do - expect(subject).to be_persisted - end - - it 'does not create a new environment' do - expect { subject }.not_to change { Environment.count } - end - - it 'updates external url' do - subject - - expect(subject.environment.name).to eq('review-apps/master') - expect(subject.environment.external_url).to eq('http://master.review-apps.gitlab.com') - end - end - - context 'when project was removed' do - let(:environment) {} - - before do - job.update(project: nil) - end - - it 'does not create deployment or environment' do - expect { subject }.not_to raise_error - - expect(Environment.count).to be_zero - expect(Deployment.count).to be_zero - end - end - end - - describe '#expanded_environment_url' do - subject { service.send(:expanded_environment_url) } - - context 'when yaml environment uses $CI_COMMIT_REF_NAME' do - let(:job) do - create(:ci_build, - ref: 'master', - options: { environment: { url: 'http://review/$CI_COMMIT_REF_NAME' } }) - end - - it { is_expected.to eq('http://review/master') } - end - - context 'when yaml environment uses $CI_ENVIRONMENT_SLUG' do - let(:job) do - create(:ci_build, - ref: 'master', - environment: 'production', - options: { environment: { url: 'http://review/$CI_ENVIRONMENT_SLUG' } }) - end - - let!(:environment) do - create(:environment, - project: job.project, - name: 'production', - slug: 'prod-slug', - external_url: 'http://review/old') - end - - it { is_expected.to eq('http://review/prod-slug') } - end - - context 'when yaml environment uses yaml_variables containing symbol keys' do - let(:job) do - create(:ci_build, - yaml_variables: [{ key: :APP_HOST, value: 'host' }], - options: { environment: { url: 'http://review/$APP_HOST' } }) - end - - it { is_expected.to eq('http://review/host') } - end - - context 'when yaml environment does not have url' do - let(:job) { create(:ci_build, environment: 'staging') } - - let!(:environment) do - create(:environment, project: job.project, name: job.environment) - end - - it 'returns the external_url from persisted environment' do - is_expected.to be_nil - end - end - end - - describe 'processing of builds' do - shared_examples 'does not create deployment' do - it 'does not create a new deployment' do - expect { subject }.not_to change { Deployment.count } - end - - it 'does not call a service' do - expect_any_instance_of(described_class).not_to receive(:execute) - - subject - end - end - - shared_examples 'creates deployment' do - it 'creates a new deployment' do - expect { subject }.to change { Deployment.count }.by(1) - end - - it 'calls a service' do - expect_any_instance_of(described_class).to receive(:execute) - - subject - end - - it 'is set as deployable' do - subject - - expect(Deployment.last.deployable).to eq(deployable) - end - - it 'updates environment URL' do - subject - - expect(Deployment.last.environment.external_url).not_to be_nil - end - end - - context 'without environment specified' do - let(:job) { create(:ci_build) } - - it_behaves_like 'does not create deployment' do - subject { job.success } - end - end - - context 'when environment is specified' do - let(:deployable) { job } - - let(:options) do - { environment: { name: 'production', url: 'http://gitlab.com' } } - end - - context 'when job succeeds' do - it_behaves_like 'creates deployment' do - subject { job.success } - end - end - - context 'when job fails' do - it_behaves_like 'does not create deployment' do - subject { job.drop } - end - end - - context 'when job is retried' do - it_behaves_like 'creates deployment' do - before do - stub_not_protect_default_branch - - project.add_developer(user) - end - - let(:deployable) { Ci::Build.retry(job, user) } - - subject { deployable.success } - end - end - end - end - - describe "merge request metrics" do - let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) } - - context "while updating the 'first_deployed_to_production_at' time" do - before do - merge_request.metrics.update!(merged_at: Time.now) - end - - context "for merge requests merged before the current deploy" do - it "sets the time if the deploy's environment is 'production'" do - time = Time.now - Timecop.freeze(time) { service.execute } - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) - end - - it "doesn't set the time if the deploy's environment is not 'production'" do - job.update(environment: 'staging') - service = described_class.new(job) - service.execute - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil - end - - it 'does not raise errors if the merge request does not have a metrics record' do - merge_request.metrics.destroy - - expect(merge_request.reload.metrics).to be_nil - expect { service.execute }.not_to raise_error - end - end - - context "for merge requests merged before the previous deploy" do - context "if the 'first_deployed_to_production_at' time is already set" do - it "does not overwrite the older 'first_deployed_to_production_at' time" do - # Previous deploy - time = Time.now - Timecop.freeze(time) { service.execute } - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) - - # Current deploy - service = described_class.new(job) - Timecop.freeze(time + 12.hours) { service.execute } - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) - end - end - - context "if the 'first_deployed_to_production_at' time is not already set" do - it "does not overwrite the older 'first_deployed_to_production_at' time" do - # Previous deploy - time = 5.minutes.from_now - Timecop.freeze(time) { service.execute } - - expect(merge_request.reload.metrics.merged_at).to be < merge_request.reload.metrics.first_deployed_to_production_at - - merge_request.reload.metrics.update(first_deployed_to_production_at: nil) - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil - - # Current deploy - service = described_class.new(job) - Timecop.freeze(time + 12.hours) { service.execute } - - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil - end - end - end - end - end -end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 07aa8449a66..bd519e7f077 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -343,7 +343,42 @@ describe Issues::UpdateService, :mailer do end end - context 'when the milestone change' do + context 'when the milestone is removed' do + let!(:non_subscriber) { create(:user) } + + let!(:subscriber) do + create(:user) do |u| + issue.toggle_subscription(u, project) + project.add_developer(u) + end + end + + it_behaves_like 'system notes for milestones' + + it 'sends notifications for subscribers of changed milestone' do + issue.milestone = create(:milestone) + + issue.save + + perform_enqueued_jobs do + update_issue(milestone_id: "") + end + + should_email(subscriber) + should_not_email(non_subscriber) + end + end + + context 'when the milestone is changed' do + let!(:non_subscriber) { create(:user) } + + let!(:subscriber) do + create(:user) do |u| + issue.toggle_subscription(u, project) + project.add_developer(u) + end + end + it 'marks todos as done' do update_issue(milestone: create(:milestone)) @@ -351,6 +386,15 @@ describe Issues::UpdateService, :mailer do end it_behaves_like 'system notes for milestones' + + it 'sends notifications for subscribers of changed milestone' do + perform_enqueued_jobs do + update_issue(milestone: create(:milestone)) + end + + should_email(subscriber) + should_not_email(non_subscriber) + end end context 'when the labels change' do @@ -374,7 +418,7 @@ describe Issues::UpdateService, :mailer do let!(:non_subscriber) { create(:user) } let!(:subscriber) do - create(:user).tap do |u| + create(:user) do |u| label.toggle_subscription(u, project) project.add_developer(u) end diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb index 21f369a3818..546c9f277c5 100644 --- a/spec/services/merge_requests/reload_diffs_service_spec.rb +++ b/spec/services/merge_requests/reload_diffs_service_spec.rb @@ -60,6 +60,17 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin subject.execute end + + it 'avoids N+1 queries', :request_store do + current_user + merge_request + + control_count = ActiveRecord::QueryRecorder.new do + subject.execute + end.count + + expect { subject.execute }.not_to exceed_query_limit(control_count) + end end end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 55dfab81c26..1b599ba11b6 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -315,7 +315,42 @@ describe MergeRequests::UpdateService, :mailer do end end - context 'when the milestone change' do + context 'when the milestone is removed' do + let!(:non_subscriber) { create(:user) } + + let!(:subscriber) do + create(:user) do |u| + merge_request.toggle_subscription(u, project) + project.add_developer(u) + end + end + + it_behaves_like 'system notes for milestones' + + it 'sends notifications for subscribers of changed milestone' do + merge_request.milestone = create(:milestone) + + merge_request.save + + perform_enqueued_jobs do + update_merge_request(milestone_id: "") + end + + should_email(subscriber) + should_not_email(non_subscriber) + end + end + + context 'when the milestone is changed' do + let!(:non_subscriber) { create(:user) } + + let!(:subscriber) do + create(:user) do |u| + merge_request.toggle_subscription(u, project) + project.add_developer(u) + end + end + it 'marks pending todos as done' do update_merge_request({ milestone: create(:milestone) }) @@ -323,6 +358,15 @@ describe MergeRequests::UpdateService, :mailer do end it_behaves_like 'system notes for milestones' + + it 'sends notifications for subscribers of changed milestone' do + perform_enqueued_jobs do + update_merge_request(milestone: create(:milestone)) + end + + should_email(subscriber) + should_not_email(non_subscriber) + end end context 'when the labels change' do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 68a361fa882..2d8da7673dc 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -13,6 +13,54 @@ describe NotificationService, :mailer do end end + shared_examples 'altered milestone notification on issue' do + it 'sends the email to the correct people' do + should_email(subscriber_to_new_milestone) + issue.assignees.each do |a| + should_email(a) + end + should_email(@u_watcher) + should_email(@u_guest_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_email(@subscribed_participant) + should_email(@watcher_and_subscriber) + should_not_email(@u_guest_custom) + should_not_email(@u_committer) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_lazy_participant) + should_not_email(issue.author) + should_not_email(@u_disabled) + should_not_email(@u_custom_global) + should_not_email(@u_mentioned) + end + end + + shared_examples 'altered milestone notification on merge request' do + it 'sends the email to the correct people' do + should_email(subscriber_to_new_milestone) + merge_request.assignees.each do |a| + should_email(a) + end + should_email(@u_watcher) + should_email(@u_guest_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_email(@subscribed_participant) + should_email(@watcher_and_subscriber) + should_not_email(@u_guest_custom) + should_not_email(@u_committer) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_lazy_participant) + should_not_email(merge_request.author) + should_not_email(@u_disabled) + should_not_email(@u_custom_global) + should_not_email(@u_mentioned) + end + end + shared_examples 'notifications for new mentions' do it 'sends no emails when no new mentions are present' do send_notifications @@ -952,6 +1000,96 @@ describe NotificationService, :mailer do end end + describe '#removed_milestone_issue' do + it_behaves_like 'altered milestone notification on issue' do + let(:milestone) { create(:milestone, project: project, issues: [issue]) } + let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } } + + before do + notification.removed_milestone_issue(issue, issue.author) + end + end + + context 'confidential issues' do + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:guest) { create(:user) } + let(:admin) { create(:admin) } + let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignees: [assignee]) } + let(:milestone) { create(:milestone, project: project, issues: [confidential_issue]) } + + it "emails subscribers of the issue's milestone that can read the issue" do + project.add_developer(member) + project.add_guest(guest) + + confidential_issue.subscribe(non_member, project) + confidential_issue.subscribe(author, project) + confidential_issue.subscribe(assignee, project) + confidential_issue.subscribe(member, project) + confidential_issue.subscribe(guest, project) + confidential_issue.subscribe(admin, project) + + reset_delivered_emails! + + notification.removed_milestone_issue(confidential_issue, @u_disabled) + + should_not_email(non_member) + should_not_email(guest) + should_email(author) + should_email(assignee) + should_email(member) + should_email(admin) + end + end + end + + describe '#changed_milestone_issue' do + it_behaves_like 'altered milestone notification on issue' do + let(:new_milestone) { create(:milestone, project: project, issues: [issue]) } + let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } } + + before do + notification.changed_milestone_issue(issue, new_milestone, issue.author) + end + end + + context 'confidential issues' do + let(:author) { create(:user) } + let(:assignee) { create(:user) } + let(:non_member) { create(:user) } + let(:member) { create(:user) } + let(:guest) { create(:user) } + let(:admin) { create(:admin) } + let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignees: [assignee]) } + let(:new_milestone) { create(:milestone, project: project, issues: [confidential_issue]) } + + it "emails subscribers of the issue's milestone that can read the issue" do + project.add_developer(member) + project.add_guest(guest) + + confidential_issue.subscribe(non_member, project) + confidential_issue.subscribe(author, project) + confidential_issue.subscribe(assignee, project) + confidential_issue.subscribe(member, project) + confidential_issue.subscribe(guest, project) + confidential_issue.subscribe(admin, project) + + reset_delivered_emails! + + notification.changed_milestone_issue(confidential_issue, new_milestone, @u_disabled) + + should_not_email(non_member) + should_not_email(guest) + should_email(author) + should_email(assignee) + should_email(member) + should_email(admin) + end + end + end + describe '#close_issue' do before do update_custom_notification(:close_issue, @u_guest_custom, resource: project) @@ -1304,6 +1442,28 @@ describe NotificationService, :mailer do end end + describe '#removed_milestone_merge_request' do + it_behaves_like 'altered milestone notification on merge request' do + let(:milestone) { create(:milestone, project: project, merge_requests: [merge_request]) } + let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } } + + before do + notification.removed_milestone_merge_request(merge_request, merge_request.author) + end + end + end + + describe '#changed_milestone_merge_request' do + it_behaves_like 'altered milestone notification on merge request' do + let(:new_milestone) { create(:milestone, project: project, merge_requests: [merge_request]) } + let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } } + + before do + notification.changed_milestone_merge_request(merge_request, new_milestone, merge_request.author) + end + end + end + describe '#merge_request_unmergeable' do it "sends email to merge request author" do notification.merge_request_unmergeable(merge_request) diff --git a/spec/services/update_deployment_service_spec.rb b/spec/services/update_deployment_service_spec.rb new file mode 100644 index 00000000000..3c55dd9659a --- /dev/null +++ b/spec/services/update_deployment_service_spec.rb @@ -0,0 +1,217 @@ +require 'spec_helper' + +describe UpdateDeploymentService do + let(:user) { create(:user) } + let(:options) { { name: 'production' } } + + let(:job) do + create(:ci_build, + ref: 'master', + tag: false, + environment: 'production', + options: { environment: options }, + project: project) + end + + let(:project) { create(:project, :repository) } + let(:environment) { deployment.environment } + let(:deployment) { job.deployment } + let(:service) { described_class.new(deployment) } + + before do + job.success! # Create/Succeed deployment + end + + describe '#execute' do + subject { service.execute } + + let(:store) { Gitlab::EtagCaching::Store.new } + + it 'invalidates the environment etag cache' do + old_value = store.get(environment.etag_cache_key) + + subject + + expect(store.get(environment.etag_cache_key)).not_to eq(old_value) + end + + it 'creates ref' do + expect_any_instance_of(Repository) + .to receive(:create_ref) + .with(deployment.ref, deployment.send(:ref_path)) + + subject + end + + it 'updates merge request metrics' do + expect_any_instance_of(Deployment) + .to receive(:update_merge_request_metrics!) + + subject + end + + context 'when start action is defined' do + let(:options) { { name: 'production', action: 'start' } } + + context 'and environment is stopped' do + before do + environment.stop + end + + it 'makes environment available' do + subject + + expect(environment.reload).to be_available + end + end + end + + context 'when variables are used' do + let(:options) do + { name: 'review-apps/$CI_COMMIT_REF_NAME', + url: 'http://$CI_COMMIT_REF_NAME.review-apps.gitlab.com' } + end + + before do + environment.update(name: 'review-apps/master') + job.update(environment: 'review-apps/$CI_COMMIT_REF_NAME') + end + + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'updates external url' do + subject + + expect(subject.environment.name).to eq('review-apps/master') + expect(subject.environment.external_url).to eq('http://master.review-apps.gitlab.com') + end + end + end + + describe '#expanded_environment_url' do + subject { service.send(:expanded_environment_url) } + + context 'when yaml environment uses $CI_COMMIT_REF_NAME' do + let(:job) do + create(:ci_build, + ref: 'master', + environment: 'production', + project: project, + options: { environment: { name: 'production', url: 'http://review/$CI_COMMIT_REF_NAME' } }) + end + + it { is_expected.to eq('http://review/master') } + end + + context 'when yaml environment uses $CI_ENVIRONMENT_SLUG' do + let(:job) do + create(:ci_build, + ref: 'master', + environment: 'prod-slug', + project: project, + options: { environment: { name: 'prod-slug', url: 'http://review/$CI_ENVIRONMENT_SLUG' } }) + end + + it { is_expected.to eq('http://review/prod-slug') } + end + + context 'when yaml environment uses yaml_variables containing symbol keys' do + let(:job) do + create(:ci_build, + yaml_variables: [{ key: :APP_HOST, value: 'host' }], + environment: 'production', + project: project, + options: { environment: { name: 'production', url: 'http://review/$APP_HOST' } }) + end + + it { is_expected.to eq('http://review/host') } + end + + context 'when yaml environment does not have url' do + let(:job) { create(:ci_build, environment: 'staging', project: project) } + + it 'returns the external_url from persisted environment' do + is_expected.to be_nil + end + end + end + + describe "merge request metrics" do + let(:merge_request) { create(:merge_request, target_branch: 'master', source_branch: 'feature', source_project: project) } + + context "while updating the 'first_deployed_to_production_at' time" do + before do + merge_request.metrics.update!(merged_at: 1.hour.ago) + end + + context "for merge requests merged before the current deploy" do + it "sets the time if the deploy's environment is 'production'" do + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) + end + + context 'when job deploys to staging' do + let(:job) do + create(:ci_build, + ref: 'master', + tag: false, + environment: 'staging', + options: { environment: { name: 'staging' } }, + project: project) + end + + it "doesn't set the time if the deploy's environment is not 'production'" do + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_nil + end + end + + it 'does not raise errors if the merge request does not have a metrics record' do + merge_request.metrics.destroy + + expect(merge_request.reload.metrics).to be_nil + expect { service.execute }.not_to raise_error + end + end + + context "for merge requests merged before the previous deploy" do + context "if the 'first_deployed_to_production_at' time is already set" do + it "does not overwrite the older 'first_deployed_to_production_at' time" do + # Previous deploy + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) + + # Current deploy + Timecop.travel(12.hours.from_now) do + service.execute + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(deployment.finished_at) + end + end + end + + context "if the 'first_deployed_to_production_at' time is not already set" do + it "does not overwrite the older 'first_deployed_to_production_at' time" do + # Previous deploy + time = 5.minutes.from_now + Timecop.freeze(time) { service.execute } + + expect(merge_request.reload.metrics.merged_at).to be < merge_request.reload.metrics.first_deployed_to_production_at + + previous_time = merge_request.reload.metrics.first_deployed_to_production_at + + # Current deploy + Timecop.freeze(time + 12.hours) { service.execute } + + expect(merge_request.reload.metrics.first_deployed_to_production_at).to eq(previous_time) + end + end + end + end + end +end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 83035788a56..ecefdc23811 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -85,7 +85,7 @@ module CycleAnalyticsHelpers raise ArgumentError end - CreateDeploymentService.new(dummy_job).execute + dummy_job.success! # State machine automatically update associated deployment/environment record end def dummy_production_job(user, project) @@ -97,7 +97,7 @@ module CycleAnalyticsHelpers end def dummy_pipeline(project) - Ci::Pipeline.new( + create(:ci_pipeline, sha: project.repository.commit('master').sha, ref: 'master', source: :push, @@ -106,9 +106,7 @@ module CycleAnalyticsHelpers end def new_dummy_job(user, project, environment) - project.environments.find_or_create_by(name: environment) - - Ci::Build.new( + create(:ci_build, project: project, user: user, environment: environment, diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index 9c9d7ad781e..95e69328080 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -34,12 +34,24 @@ shared_examples 'issuable notes filter' do expect(user.reload.notes_filter_for(issuable)).to eq(0) end - it 'returns no system note' do + it 'returns only user comments' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + discussions = JSON.parse(response.body) - expect(JSON.parse(response.body).count).to eq(1) + expect(discussions.count).to eq(1) + expect(discussions.first["notes"].first["system"]).to be(false) + end + + it 'returns only activity notes' do + user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable) + + get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid + discussions = JSON.parse(response.body) + + expect(discussions.count).to eq(1) + expect(discussions.first["notes"].first["system"]).to be(true) end context 'when filter is set to "only_comments"' do diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb index ef9bb7f5533..361d4220c6e 100644 --- a/spec/support/shared_examples/helm_generated_script.rb +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -3,12 +3,6 @@ shared_examples 'helm commands' do let(:helm_setup) do <<~EOS set -eo pipefail - ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1,2) - echo http://mirror.clarkson.edu/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories - echo http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories - apk add -U wget ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ EOS end diff --git a/spec/views/projects/tree/_blob_item.html.haml_spec.rb b/spec/views/projects/tree/_tree_row.html.haml_spec.rb index 6a477c712ff..3353b7665e2 100644 --- a/spec/views/projects/tree/_blob_item.html.haml_spec.rb +++ b/spec/views/projects/tree/_tree_row.html.haml_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'projects/tree/_blob_item' do +describe 'projects/tree/_tree_row' do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:blob_item) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files/ruby').first } @@ -31,10 +31,7 @@ describe 'projects/tree/_blob_item' do end end - def render_partial(blob_item) - render partial: 'projects/tree/blob_item', locals: { - blob_item: blob_item, - type: 'blob' - } + def render_partial(items) + render partial: 'projects/tree/tree_row', collection: [items].flatten end end diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index dba70883130..5eb9709ded9 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -2,15 +2,39 @@ require 'spec_helper' describe BuildSuccessWorker do describe '#perform' do + subject { described_class.new.perform(build.id) } + + before do + allow_any_instance_of(Deployment).to receive(:create_ref) + end + context 'when build exists' do - context 'when build belogs to the environment' do - let!(:build) { create(:ci_build, environment: 'production') } + context 'when deployment was not created with the build creation' do # An edge case during the transition period + let!(:build) { create(:ci_build, :deploy_to_production) } + + before do + Deployment.delete_all + build.reload + end - it 'executes deployment service' do - expect_any_instance_of(CreateDeploymentService) - .to receive(:execute) + it 'creates a successful deployment' do + expect(build).not_to be_has_deployment - described_class.new.perform(build.id) + subject + + build.reload + expect(build).to be_has_deployment + expect(build.deployment).to be_success + end + end + + context 'when deployment was created with the build creation' do # Counter part of the above edge case + let!(:build) { create(:ci_build, :deploy_to_production) } + + it 'does not create a new deployment' do + expect(build).to be_has_deployment + + expect { subject }.not_to change { Deployment.count } end end @@ -18,10 +42,22 @@ describe BuildSuccessWorker do let!(:build) { create(:ci_build, project: nil) } it 'does not create deployment' do - expect_any_instance_of(CreateDeploymentService) - .not_to receive(:execute) + subject + + expect(build.reload).not_to be_has_deployment + end + end + + context 'when the build will stop an environment' do + let!(:build) { create(:ci_build, :stop_review_app, environment: environment.name, project: environment.project) } + let(:environment) { create(:environment, state: :available) } + + it 'stops the environment' do + expect(environment).to be_available + + subject - described_class.new.perform(build.id) + expect(environment.reload).to be_stopped end end end diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_platform_configure_worker_spec.rb new file mode 100644 index 00000000000..1a7ad8923f6 --- /dev/null +++ b/spec/workers/cluster_platform_configure_worker_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClusterPlatformConfigureWorker, '#execute' do + context 'when provider type is gcp' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + + it 'configures kubernetes platform' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + + described_class.new.perform(cluster.id) + end + end + + context 'when provider type is user' do + let(:cluster) { create(:cluster, :project, :provided_by_user) } + + it 'configures kubernetes platform' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + + described_class.new.perform(cluster.id) + end + end + + context 'when cluster does not exist' do + it 'does not provision a cluster' do + expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute) + + described_class.new.perform(123) + end + end +end diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb index 8054ec11a48..0a2dfef36a4 100644 --- a/spec/workers/cluster_provision_worker_spec.rb +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -14,18 +14,25 @@ describe ClusterProvisionWorker do end context 'when provider type is user' do - let(:cluster) { create(:cluster, provider_type: :user) } + let(:cluster) { create(:cluster, :provided_by_user) } it 'does not provision a cluster' do expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) described_class.new.perform(cluster.id) end + + it 'configures kubernetes platform' do + expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id) + + described_class.new.perform(cluster.id) + end end context 'when cluster does not exist' do it 'does not provision a cluster' do expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) + expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async) described_class.new.perform(123) end diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb new file mode 100644 index 00000000000..ba7d45eca01 --- /dev/null +++ b/spec/workers/deployments/success_worker_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Deployments::SuccessWorker do + subject { described_class.new.perform(deployment&.id) } + + context 'when successful deployment' do + let(:deployment) { create(:deployment, :success) } + + it 'executes UpdateDeploymentService' do + expect(UpdateDeploymentService) + .to receive(:new).with(deployment).and_call_original + + subject + end + end + + context 'when canceled deployment' do + let(:deployment) { create(:deployment, :canceled) } + + it 'does not execute UpdateDeploymentService' do + expect(UpdateDeploymentService).not_to receive(:new) + + subject + end + end + + context 'when deploy record does not exist' do + let(:deployment) { nil } + + it 'does not execute UpdateDeploymentService' do + expect(UpdateDeploymentService).not_to receive(:new) + + subject + end + end +end |